test-patient-generator 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +6 -8
- data/lib/test-patient-generator.rb +1 -6
- data/lib/tpg/ext/coded.rb +9 -3
- data/lib/tpg/ext/data_criteria.rb +150 -142
- data/lib/tpg/ext/range.rb +2 -132
- data/lib/tpg/ext/value.rb +21 -82
- data/lib/tpg/generation/exporter.rb +14 -32
- data/lib/tpg/generation/generator.rb +158 -84
- data/lib/tpg/generation/randomizer.rb +17 -30
- metadata +83 -9
- data/lib/tpg/ext/conjunction.rb +0 -23
- data/lib/tpg/ext/derivation_operator.rb +0 -49
- data/lib/tpg/ext/population_criteria.rb +0 -12
- data/lib/tpg/ext/precondition.rb +0 -23
- data/lib/tpg/ext/subset_operator.rb +0 -7
- data/lib/tpg/ext/temporal_reference.rb +0 -66
data/lib/tpg/ext/value.rb
CHANGED
@@ -1,83 +1,38 @@
|
|
1
1
|
module HQMF
|
2
2
|
class Value
|
3
|
-
|
4
|
-
|
5
|
-
# Perform a deep copy of this Value.
|
3
|
+
# Translate a Ruby time object to an HL7 timestamp string (YYYYMMDD).
|
6
4
|
#
|
7
|
-
# @return
|
8
|
-
def
|
9
|
-
|
10
|
-
end
|
11
|
-
|
12
|
-
#
|
13
|
-
def format
|
14
|
-
# unit_mapping = {"a" => "years", "mo" => "months", "wk" => "weeks", "d" => "days"}
|
15
|
-
# pretty_unit = unit_mapping[unit] if unit
|
16
|
-
# pretty_unit ||= unit
|
17
|
-
|
18
|
-
{ "scalar" => value, "units" => unit }
|
5
|
+
# @return An HL7 timestamp string (YYYYMMDD) equivalent to time.
|
6
|
+
def self.time_to_ts(time)
|
7
|
+
time.strftime("%Y%m%d%H%M%S")
|
19
8
|
end
|
20
9
|
|
21
|
-
#
|
10
|
+
# Translate an HQMF Value object into a shape that HealthDataStandards understands.
|
22
11
|
#
|
23
|
-
# @
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
return value2 if value1.nil?
|
30
|
-
|
31
|
-
if value1.type == "PQ" && value2.type == "PQ"
|
32
|
-
# TODO Combine the value of two PQs. This will be tough if there is a case of time units and no relative TS
|
33
|
-
|
34
|
-
elsif value1.type == "TS" && value2.type == "TS"
|
35
|
-
# Convert the time strings that we have into actual time objects
|
36
|
-
|
37
|
-
else
|
38
|
-
# One PQ and one TS
|
39
|
-
pq = value1.type == "PQ" ? value1 : value2
|
40
|
-
ts = value1.type == "TS" ? value1 : value2
|
41
|
-
|
42
|
-
# Create a Ruby object to represent the TS
|
43
|
-
year = ts.value[0,4]
|
44
|
-
month = ts.value[4,2]
|
45
|
-
day = ts.value[6,2]
|
46
|
-
time = Time.new(year, month, day)
|
47
|
-
|
48
|
-
# Advance that time forward the amount the PQ specifies. Convert units to symbols for advance function.
|
49
|
-
pq.value += 1 unless pq.inclusive?
|
50
|
-
unit_mapping = {"a" => :years, "mo" => :months, "wk" => :weeks, "d" => :days}
|
51
|
-
time = time.advance({unit_mapping[pq.unit] => pq.value.to_i})
|
52
|
-
|
53
|
-
# Form up the modified TS with expected YYYYMMDD formatting (avoid YYYYMD)
|
54
|
-
year = time.year
|
55
|
-
month = time.month < 10 ? "0#{time.month}" : time.month
|
56
|
-
day = time.day < 10 ? "0#{time.day}" : time.day
|
57
|
-
|
58
|
-
Value.new("TS", value1.unit, "#{year}#{month}#{day}", value1.inclusive? && value2.inclusive?, false, false)
|
12
|
+
# @return A Value formatted for storing a HealthDataStandards Record.
|
13
|
+
def format
|
14
|
+
if type == "PQ"
|
15
|
+
{ "scalar" => value, "units" => unit }
|
16
|
+
elsif type == "TS"
|
17
|
+
to_seconds
|
59
18
|
end
|
60
19
|
end
|
61
20
|
|
62
|
-
|
63
|
-
year = time.year
|
64
|
-
month = time.month < 10 ? "0#{time.month}" : time.month
|
65
|
-
day = time.day < 10 ? "0#{time.day}" : time.day
|
66
|
-
|
67
|
-
"#{year}#{month}#{day}"
|
68
|
-
end
|
69
|
-
|
21
|
+
# Translate the time stored in this Value into epoch seconds.
|
70
22
|
#
|
71
|
-
#
|
72
|
-
# @return
|
23
|
+
# @return Epoch seconds equivalent to the time stored in this Value.
|
73
24
|
def to_seconds
|
74
|
-
|
25
|
+
return nil unless type == "TS"
|
26
|
+
|
27
|
+
to_time_object.utc.to_i
|
75
28
|
end
|
76
29
|
|
77
|
-
#
|
30
|
+
# Translate the time represented by this Value into a Ruby time object.
|
78
31
|
#
|
79
|
-
# @return
|
32
|
+
# @return A Ruby time object equivalent to the time represented by this Value.
|
80
33
|
def to_time_object
|
34
|
+
return nil unless type == "TS"
|
35
|
+
|
81
36
|
year = value[0,4].to_i
|
82
37
|
month = value[4,2].to_i
|
83
38
|
day = value[6,2].to_i
|
@@ -90,23 +45,7 @@ module HQMF
|
|
90
45
|
second = value[12,2].to_i
|
91
46
|
end
|
92
47
|
|
93
|
-
Time.
|
94
|
-
end
|
95
|
-
|
96
|
-
def <=>(value)
|
97
|
-
# So far there has only been a need to compare TS Values
|
98
|
-
if type == "TS" && value.type == "TS"
|
99
|
-
time = to_time_object
|
100
|
-
other = value.value
|
101
|
-
|
102
|
-
if time < other
|
103
|
-
-1
|
104
|
-
elsif time > other
|
105
|
-
1
|
106
|
-
else
|
107
|
-
0
|
108
|
-
end
|
109
|
-
end
|
48
|
+
Time.gm(year, month, day, hour, minute, second)
|
110
49
|
end
|
111
50
|
end
|
112
51
|
end
|
@@ -36,11 +36,12 @@ module TPG
|
|
36
36
|
file
|
37
37
|
end
|
38
38
|
|
39
|
-
# Export QRDA Category 1 patients to a zip file.
|
39
|
+
# Export QRDA Category 1 patients to a zip file.
|
40
|
+
# Contents are organized with a directory for each measure containing one patient for validation.
|
40
41
|
#
|
41
42
|
# @param [Hash] measure_patients Measures mapped to the patient that was generated for it.
|
42
43
|
# @return A zip file containing all of the QRDA Category 1 patients that were passed in.
|
43
|
-
def self.
|
44
|
+
def self.zip_qrda_html_patients(measure_patients)
|
44
45
|
file = Tempfile.new("patients-#{Time.now.to_i}")
|
45
46
|
|
46
47
|
Zip::ZipOutputStream.open(file.path) do |zip|
|
@@ -55,40 +56,21 @@ module TPG
|
|
55
56
|
file.close
|
56
57
|
file
|
57
58
|
end
|
58
|
-
|
59
|
-
# Export
|
60
|
-
#
|
59
|
+
|
60
|
+
# Export QRDA Category 1 patients to a zip file.
|
61
|
+
# Contents are organized with a directory for each measure containing one patient for validation.
|
61
62
|
#
|
62
|
-
# @param [
|
63
|
-
# @
|
64
|
-
|
65
|
-
def self.zip_bundle(patients, name, version)
|
63
|
+
# @param [Hash] measure_patients Measures mapped to the patient that was generated for it.
|
64
|
+
# @return A zip file containing all of the QRDA Category 1 patients that were passed in.
|
65
|
+
def self.zip_qrda_cat_1_patients(measure_patients, measures)
|
66
66
|
file = Tempfile.new("patients-#{Time.now.to_i}")
|
67
67
|
|
68
68
|
Zip::ZipOutputStream.open(file.path) do |zip|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
patients.each_with_index do |patient, index|
|
75
|
-
filename = "#{index}_#{patient_filename(patient)}"
|
76
|
-
|
77
|
-
# Define path names
|
78
|
-
c32_path = File.join("patients", "c32", "#{filename}.xml")
|
79
|
-
ccr_path = File.join("patients", "ccr", "#{filename}.xml")
|
80
|
-
html_path = File.join("patients", "html", "#{filename}.html")
|
81
|
-
json_path = File.join("patients", "json", "#{filename}.json")
|
82
|
-
|
83
|
-
# For each patient add a C32, CCR, HTML, and JSON file.
|
84
|
-
zip.put_next_entry(c32_path)
|
85
|
-
zip << HealthDataStandards::Export::C32.export(patient)
|
86
|
-
zip.put_next_entry(ccr_path)
|
87
|
-
zip << HealthDataStandards::Export::CCR.export(patient)
|
88
|
-
zip.put_next_entry(html_path)
|
89
|
-
zip << html_contents(patient)
|
90
|
-
zip.put_next_entry(json_path)
|
91
|
-
zip << JSON.pretty_generate(JSON.parse(patient.to_json))
|
69
|
+
measure_patients.each do |nqf_id, patient|
|
70
|
+
measure_defs = measures.find {|m| m.id == nqf_id}
|
71
|
+
# Create a directory for this measure and insert the HTML for this patient.
|
72
|
+
zip.put_next_entry(File.join(measure_defs.hqmf_id, "#{patient_filename(patient)}.xml"))
|
73
|
+
zip << QrdaGenerator::Export::Cat1.export(patient, [measure_defs], Time.gm(2011, 1, 1), Time.gm(2011, 12, 31))
|
92
74
|
end
|
93
75
|
end
|
94
76
|
|
@@ -1,49 +1,33 @@
|
|
1
1
|
module HQMF
|
2
|
-
# The generator will create as many patients as possible to exhaustively test the logic of a given clinical quality measure.
|
3
2
|
class Generator
|
4
|
-
# TODO - This is a hack and a half. Need a better way to resolve data_criteria from any point in the tree.
|
5
|
-
class << self
|
6
|
-
attr_accessor :hqmf
|
7
|
-
attr_accessor :value_sets
|
8
|
-
end
|
9
|
-
|
10
|
-
# @param [HQMF::Document] hqmf A model representing the logic of a given HQMF document.
|
11
|
-
# @param [Hash] value_sets All of the value sets referenced by this particular HQMF document.
|
12
|
-
def initialize(hqmf, value_sets)
|
13
|
-
@patients = []
|
14
|
-
Generator.hqmf = hqmf
|
15
|
-
Generator.value_sets = value_sets
|
16
|
-
end
|
17
|
-
|
18
3
|
# Generate patients from lists of DataCriteria. This is originally created for QRDA Category 1 validation testing,
|
19
4
|
# i.e. a single patient will be generated per measure with an entry for every data criteria involved in the measure.
|
20
5
|
#
|
21
|
-
# @param [Hash] measure_needs A hash of measure IDs mapped to a list of all their data criteria.
|
22
|
-
# @param [Hash] measure_value_sets A hash of measure IDs mapped to hashes of value sets used by the DataCriteria in measure_needs.
|
6
|
+
# @param [Hash] measure_needs A hash of measure IDs mapped to a list of all their data criteria in JSON.
|
23
7
|
# @return [Hash] A hash of measure IDs mapped to a Record that includes all the given data criteria (values and times are arbitrary).
|
24
|
-
def self.generate_qrda_patients(measure_needs
|
8
|
+
def self.generate_qrda_patients(measure_needs)
|
25
9
|
return {} if measure_needs.nil?
|
26
10
|
|
27
11
|
measure_patients = {}
|
28
12
|
measure_needs.each do |measure, all_data_criteria|
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
index = unique_data_criteria.index {|dc| dc.code_list_id == data_criteria.code_list_id && dc.negation_code_list_id == data_criteria.negation_code_list_id && dc.field_values == data_criteria.field_values && dc.status == data_criteria.status}
|
34
|
-
unique_data_criteria << data_criteria if index.nil?
|
35
|
-
end
|
13
|
+
# Define a list of unique data criteria and matching value sets to create a patient for this measure.
|
14
|
+
unique_data_criteria = select_unique_data_criteria(all_data_criteria)
|
15
|
+
oids = select_unique_oids(all_data_criteria)
|
16
|
+
value_sets = create_oid_dictionary(oids)
|
36
17
|
|
37
18
|
# Create a patient that includes an entry for every data criteria included in this measure.
|
38
19
|
patient = Generator.create_base_patient
|
39
20
|
unique_data_criteria.each do |data_criteria|
|
40
21
|
# Ignore data criteria that are really just containers.
|
41
22
|
next if data_criteria.derivation_operator.present?
|
42
|
-
|
43
|
-
#
|
44
|
-
time =
|
45
|
-
data_criteria
|
23
|
+
|
24
|
+
# Prepare and apply our parameters for modifying the patient based on the data criteria.
|
25
|
+
time = select_valid_time_range(patient, data_criteria)
|
26
|
+
apply_field_defaults(data_criteria, time)
|
27
|
+
data_criteria.modify_patient(patient, time, value_sets)
|
46
28
|
end
|
29
|
+
|
30
|
+
# Add final data for the patient, e.g. that they were designed for the measure, possibly a birthdate, etc.
|
47
31
|
patient.measure_ids ||= []
|
48
32
|
patient.measure_ids << measure
|
49
33
|
patient.type = "qrda"
|
@@ -53,45 +37,9 @@ module HQMF
|
|
53
37
|
measure_patients
|
54
38
|
end
|
55
39
|
|
56
|
-
# Generate patients from an HQMF file and its matching value sets file. These patients are designed to test all
|
57
|
-
# paths through the logic of this particular clinical quality measure.
|
58
|
-
def generate_patients
|
59
|
-
base_patients = [Generator.create_base_patient]
|
60
|
-
generated_patients = []
|
61
|
-
|
62
|
-
# Gather all available populations. Each kind of population (e.g. IPP, DENOM) can have many multiples (e.g. IPP_1, IPP_2)
|
63
|
-
populations = []
|
64
|
-
["IPP", "DENOM", "NUMER", "EXCL", "DENEXCEP"].each do |population|
|
65
|
-
i = 1
|
66
|
-
populations << population
|
67
|
-
while Generator.hqmf.population_criteria("#{population}_#{i}").present? do
|
68
|
-
populations << "#{population}_#{i}"
|
69
|
-
i += 1
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
populations = ["EXCL_1"]
|
74
|
-
|
75
|
-
populations.each do |population|
|
76
|
-
criteria = Generator.hqmf.population_criteria(population)
|
77
|
-
|
78
|
-
# We don't need to do anything for populations with nothing specified
|
79
|
-
next if criteria.nil? || !criteria.preconditions.present?
|
80
|
-
criteria.generate(base_patients)
|
81
|
-
|
82
|
-
# Mark the patient we just created with its expected population. Then extend the Record to be augmented by the next population.
|
83
|
-
base_patients.collect! do |patient|
|
84
|
-
generated_patients.push(Generator.finalize_patient(patient))
|
85
|
-
Generator.extend_patient(patient)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
generated_patients
|
90
|
-
end
|
91
|
-
|
92
40
|
# Create a patient with trivial demographic information and no coded entries.
|
93
41
|
#
|
94
|
-
# @return A Record with a blank slate
|
42
|
+
# @return A Record with a blank slate.
|
95
43
|
def self.create_base_patient(initial_attributes = nil)
|
96
44
|
patient = Record.new
|
97
45
|
|
@@ -100,20 +48,10 @@ module HQMF
|
|
100
48
|
else
|
101
49
|
initial_attributes.each {|attribute, value| patient.send("#{attribute}=", value)}
|
102
50
|
end
|
103
|
-
patient.medical_record_number = Digest::MD5.hexdigest("#{patient.first} #{patient.last}")
|
104
51
|
|
105
52
|
patient
|
106
53
|
end
|
107
|
-
|
108
|
-
# Take an existing patient with some coded entries on them and redefine their trivial demographic information
|
109
|
-
#
|
110
|
-
# @param [Record] base_patient The patient that we're using as a base to create a new one
|
111
|
-
# @return A new Record with an identical medical history to the given patient but new trivial demographic information
|
112
|
-
def self.extend_patient(base_patient)
|
113
|
-
patient = base_patient.clone()
|
114
|
-
Randomizer.randomize_demographics(patient)
|
115
|
-
end
|
116
|
-
|
54
|
+
|
117
55
|
# Fill in any missing details that should be filled in on a patient. These include: age, gender, and first name.
|
118
56
|
#
|
119
57
|
# @param [Record] patient The patient for whom we are about to fill in remaining demographic information.
|
@@ -125,19 +63,155 @@ module HQMF
|
|
125
63
|
end
|
126
64
|
|
127
65
|
if patient.gender.nil?
|
128
|
-
# Set gender
|
129
66
|
patient.gender = "F"
|
130
|
-
|
131
|
-
patient.first = Randomizer.randomize_first_name(patient.gender)
|
67
|
+
patient.first ||= Randomizer.randomize_first_name(patient.gender)
|
132
68
|
end
|
133
69
|
|
134
70
|
patient
|
135
71
|
end
|
136
|
-
|
72
|
+
|
73
|
+
# Select all unique data criteria from a list. Category 1 validation is only checking for ability to access information
|
74
|
+
# so to minimize time we only want to include each kind of data once.
|
137
75
|
#
|
76
|
+
# @param [Array] all_data_criteria A list of HQMF::DataCriteria to be sifted through.
|
77
|
+
# @return The unique list of data criteria extracted from all_data_criteria
|
78
|
+
def self.select_unique_data_criteria(all_data_criteria)
|
79
|
+
all_data_criteria.flatten!
|
80
|
+
all_data_criteria.uniq!
|
81
|
+
|
82
|
+
unique_data_criteria = []
|
83
|
+
all_data_criteria.each do |data_criteria|
|
84
|
+
index = unique_data_criteria.index {|dc| dc.code_list_id == data_criteria.code_list_id && dc.negation_code_list_id == data_criteria.negation_code_list_id && dc.field_values == data_criteria.field_values && dc.status == data_criteria.status}
|
85
|
+
unique_data_criteria << data_criteria if index.nil?
|
86
|
+
end
|
87
|
+
|
88
|
+
unique_data_criteria
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
138
92
|
#
|
139
|
-
# @param [
|
93
|
+
# @param [Array] oids
|
140
94
|
# @return
|
95
|
+
def self.create_oid_dictionary(oids)
|
96
|
+
value_sets = []
|
97
|
+
HealthDataStandards::SVS::ValueSet.any_in(oid: oids).each do |value_set|
|
98
|
+
code_sets = value_set.concepts.map { |concept| {"code_set" => concept.code_system_name, "codes" => [concept.code]} }
|
99
|
+
value_sets << {"code_sets" => code_sets, "oid" => value_set.oid, "concept" => value_set.display_name}
|
100
|
+
end
|
101
|
+
|
102
|
+
value_sets
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
#
|
107
|
+
# @param [Array] all_data_criteria
|
108
|
+
# @return
|
109
|
+
def self.select_unique_oids(all_data_criteria)
|
110
|
+
oids = []
|
111
|
+
all_data_criteria.each do |dc|
|
112
|
+
oids << dc.code_list_id if dc.code_list_id.present?
|
113
|
+
oids << dc.negation_code_list_id if dc.negation_code_list_id.present?
|
114
|
+
oids << dc.value.code_list_id if dc.value.present? && dc.value.type == "CD"
|
115
|
+
|
116
|
+
dc.field_values.each {|name, field| oids << field.code_list_id if field.present? && field.type == "CD"} if dc.field_values.present?
|
117
|
+
end
|
118
|
+
|
119
|
+
oids << "2.16.840.1.113883.3.117.1.7.1.70"
|
120
|
+
oids << "2.16.840.1.113883.3.117.2.7.1.14"
|
121
|
+
|
122
|
+
oids.flatten!
|
123
|
+
oids.uniq!
|
124
|
+
oids.compact
|
125
|
+
end
|
126
|
+
|
127
|
+
# Create a random time range for an entry to occur. It is guaranteed to be within the lifespan of the patient and will last no longer than a day.
|
128
|
+
#
|
129
|
+
# @param [Record] patient The patient for whom this range is being generated.
|
130
|
+
# @param [HQMF::DataCriteria] data_criteria The data criteria for which we're creating an entry.
|
131
|
+
# @return A time range that can be used to create an entry for this data criteria.
|
132
|
+
def self.select_valid_time_range(patient, data_criteria)
|
133
|
+
earliest_time = patient.birthdate
|
134
|
+
latest_time = patient.deathdate
|
135
|
+
|
136
|
+
# Make sure all ranges occur within the bounds of birth and death. If this data criteria is deciding one of those two, place this range outside of our 35 year range for entries.
|
137
|
+
if data_criteria.property.present?
|
138
|
+
if data_criteria.property == :birthtime
|
139
|
+
earliest_time = HQMF::Randomizer.randomize_birthdate(patient)
|
140
|
+
latest_time = earliest_time.advance(days: 1)
|
141
|
+
elsif data_criteria.property == :expired
|
142
|
+
earliest_time = Time.now
|
143
|
+
latest_time = earliest_time.advance(days: 1)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
time = Randomizer.randomize_range(earliest_time, latest_time, {days: 1})
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
#
|
152
|
+
# @param [HQMF::DataCriteria] date_criteria
|
153
|
+
# @return
|
154
|
+
def self.apply_field_defaults(data_criteria, time)
|
155
|
+
return nil if data_criteria.field_values.nil?
|
156
|
+
|
157
|
+
# Some fields come in with no value or marked as AnyValue (i.e. any value is acceptable, there just must be one). If that's the case, we pick a default here.
|
158
|
+
data_criteria.field_values.each do |name, field|
|
159
|
+
if field.is_a? HQMF::AnyValue
|
160
|
+
if ["ADMISSION_DATETIME", "START_DATETIME", "INCISION_DATETIME"].include? name
|
161
|
+
data_criteria.field_values[name] = time.low
|
162
|
+
elsif ["DISCHARGE_DATETIME", "STOP_DATETIME", "REMOVAL_DATETIME"].include? name
|
163
|
+
data_criteria.field_values[name] = time.high
|
164
|
+
elsif name == "REASON"
|
165
|
+
# If we're not explicitly given a code (e.g. HQMF dictates there must be a reason but any is ok), we assign a random one (birth)
|
166
|
+
data_criteria.field_values[name] = Coded.for_code_list("2.16.840.1.113883.3.117.1.7.1.70", "birth")
|
167
|
+
elsif name == "ORDINAL"
|
168
|
+
# If we're not explicitly given a code (e.g. HQMF dictates there must be a reason but any is ok), we assign it to be not principle
|
169
|
+
data_criteria.field_values[name] = Coded.for_code_list("2.16.840.1.113883.3.117.2.7.1.14", "principle")
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Takes an Array of meassures and builds a Hash keyed by NQF ID with the values being an Array of data criteria.
|
176
|
+
#
|
177
|
+
# @param [Array] measures A list of HQMF::Documents for which patients will be generated.
|
178
|
+
# @return A hash of measure IDs for which we're generating patients, mapped to an array of HQMF::DataCriteria.
|
179
|
+
def self.determine_measure_needs(measures)
|
180
|
+
measure_needs = {}
|
181
|
+
measures.each do |measure|
|
182
|
+
measure_needs[measure.id] = measure.all_data_criteria
|
183
|
+
end
|
184
|
+
|
185
|
+
measure_needs
|
186
|
+
end
|
187
|
+
|
188
|
+
# Parses a JSON representation of a measure from a Bonnie Bundle into an hqmf-parser ready format.
|
189
|
+
#
|
190
|
+
# @param [Hash] measure JSON representation of a measure
|
191
|
+
# @return Tweaked JSON that has fields in the places hqmf-parser expects
|
192
|
+
def self.parse_measure(measure_json)
|
193
|
+
# HQMF Parser expects just a hash of ID => data_criteria, so translate to that format here.
|
194
|
+
translated_data_criteria = {}
|
195
|
+
measure_json["data_criteria"].each { |data_criteria| translated_data_criteria[data_criteria.keys.first] = data_criteria.values.first }
|
196
|
+
measure_json["data_criteria"] = translated_data_criteria
|
197
|
+
|
198
|
+
# HQMF::Documents have fields for hqmf_id and id, but not NQF ID. We'll store NQF_ID in ID.
|
199
|
+
measure_json["id"] = measure_json["nqf_id"]
|
200
|
+
measure_json["source_data_criteria"] = []
|
201
|
+
|
202
|
+
measure = HQMF::Document.from_json(measure_json)
|
203
|
+
measure.all_data_criteria.each do |data_criteria|
|
204
|
+
data_criteria.values ||= []
|
205
|
+
data_criteria.values << data_criteria.value if data_criteria.value && data_criteria.value.type != "ANYNonNull"
|
206
|
+
end
|
207
|
+
|
208
|
+
measure
|
209
|
+
end
|
210
|
+
|
211
|
+
# Map all patient api coded entry types from HQMF data criteria to Record sections.
|
212
|
+
#
|
213
|
+
# @param [String] type The type of the coded entry requried by a data criteria.
|
214
|
+
# @return The section type for the given patient api function type
|
141
215
|
def self.classify_entry(type)
|
142
216
|
# The possible matches per patientAPI function can be found in hqmf-parser's README
|
143
217
|
case type
|
@@ -146,7 +220,7 @@ module HQMF
|
|
146
220
|
when :proceduresPerformed
|
147
221
|
"procedures"
|
148
222
|
when :procedureResults
|
149
|
-
"
|
223
|
+
"procedures"
|
150
224
|
when :laboratoryTests
|
151
225
|
"vital_signs"
|
152
226
|
when :allMedications
|
@@ -166,4 +240,4 @@ module HQMF
|
|
166
240
|
end
|
167
241
|
end
|
168
242
|
end
|
169
|
-
end
|
243
|
+
end
|