hqmf-parser 1.0.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/Gemfile +23 -0
- data/README.md +903 -0
- data/Rakefile +19 -0
- data/VERSION +1 -0
- data/lib/hqmf-generator/hqmf-generator.rb +308 -0
- data/lib/hqmf-model/attribute.rb +35 -0
- data/lib/hqmf-model/data_criteria.rb +322 -0
- data/lib/hqmf-model/document.rb +172 -0
- data/lib/hqmf-model/population_criteria.rb +90 -0
- data/lib/hqmf-model/precondition.rb +85 -0
- data/lib/hqmf-model/types.rb +318 -0
- data/lib/hqmf-model/utilities.rb +52 -0
- data/lib/hqmf-parser.rb +54 -0
- data/lib/hqmf-parser/1.0/attribute.rb +68 -0
- data/lib/hqmf-parser/1.0/comparison.rb +34 -0
- data/lib/hqmf-parser/1.0/data_criteria.rb +105 -0
- data/lib/hqmf-parser/1.0/document.rb +209 -0
- data/lib/hqmf-parser/1.0/expression.rb +52 -0
- data/lib/hqmf-parser/1.0/population_criteria.rb +79 -0
- data/lib/hqmf-parser/1.0/precondition.rb +89 -0
- data/lib/hqmf-parser/1.0/range.rb +65 -0
- data/lib/hqmf-parser/1.0/restriction.rb +157 -0
- data/lib/hqmf-parser/1.0/utilities.rb +41 -0
- data/lib/hqmf-parser/2.0/data_criteria.rb +319 -0
- data/lib/hqmf-parser/2.0/document.rb +165 -0
- data/lib/hqmf-parser/2.0/population_criteria.rb +53 -0
- data/lib/hqmf-parser/2.0/precondition.rb +44 -0
- data/lib/hqmf-parser/2.0/types.rb +223 -0
- data/lib/hqmf-parser/2.0/utilities.rb +30 -0
- data/lib/hqmf-parser/converter/pass1/data_criteria_converter.rb +254 -0
- data/lib/hqmf-parser/converter/pass1/document_converter.rb +183 -0
- data/lib/hqmf-parser/converter/pass1/population_criteria_converter.rb +135 -0
- data/lib/hqmf-parser/converter/pass1/precondition_converter.rb +164 -0
- data/lib/hqmf-parser/converter/pass1/precondition_extractor.rb +159 -0
- data/lib/hqmf-parser/converter/pass1/simple_data_criteria.rb +35 -0
- data/lib/hqmf-parser/converter/pass1/simple_operator.rb +89 -0
- data/lib/hqmf-parser/converter/pass1/simple_population_criteria.rb +10 -0
- data/lib/hqmf-parser/converter/pass1/simple_precondition.rb +63 -0
- data/lib/hqmf-parser/converter/pass1/simple_restriction.rb +64 -0
- data/lib/hqmf-parser/converter/pass2/comparison_converter.rb +91 -0
- data/lib/hqmf-parser/converter/pass2/operator_converter.rb +169 -0
- data/lib/hqmf-parser/converter/pass3/specific_occurrence_converter.rb +86 -0
- data/lib/hqmf-parser/converter/pass3/specific_occurrence_converter_bak.rb +70 -0
- data/lib/hqmf-parser/parser.rb +22 -0
- data/lib/hqmf-parser/value_sets/value_set_parser.rb +206 -0
- data/lib/tasks/coverme.rake +8 -0
- data/lib/tasks/hqmf.rake +141 -0
- data/lib/tasks/value_sets.rake +23 -0
- metadata +159 -0
@@ -0,0 +1,169 @@
|
|
1
|
+
module HQMF
|
2
|
+
# Class for converting an HQMF 1.0 representation to an HQMF 2.0 representation
|
3
|
+
class OperatorConverter
|
4
|
+
|
5
|
+
def self.apply_temporal(data_criteria, precondition, restriction, data_criteria_converter)
|
6
|
+
data_criteria.temporal_references ||= []
|
7
|
+
value = restriction.operator.value
|
8
|
+
type = restriction.operator.type
|
9
|
+
temporal_reference = nil
|
10
|
+
if (restriction.single_target?)
|
11
|
+
# multiple targets appears to be the result of restrictions with restrictions
|
12
|
+
target = restriction.target
|
13
|
+
if (restriction.multi_target?)
|
14
|
+
found = false
|
15
|
+
# restrictions with restrictions can have a target that is modified by the child restrcitons
|
16
|
+
restriction.preconditions.each do |precondition|
|
17
|
+
if precondition.reference.id.start_with? target
|
18
|
+
found = true
|
19
|
+
target = precondition.reference.id
|
20
|
+
end
|
21
|
+
end
|
22
|
+
unless found
|
23
|
+
puts "\tmultiple targets... need to check this" if restriction.multi_target?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
temporal_reference = HQMF::TemporalReference.new(type, HQMF::Reference.new(target),value)
|
27
|
+
data_criteria_converter.validate_not_deleted(target)
|
28
|
+
elsif (restriction.multi_target?)
|
29
|
+
|
30
|
+
children_criteria = HQMF::DataCriteriaConverter.extract_data_criteria(restriction.preconditions, data_criteria_converter)
|
31
|
+
|
32
|
+
if (children_criteria.length == 1)
|
33
|
+
target = children_criteria[0].id
|
34
|
+
temporal_reference = HQMF::TemporalReference.new(type, HQMF::Reference.new(target),value)
|
35
|
+
data_criteria_converter.validate_not_deleted(target)
|
36
|
+
else
|
37
|
+
parent_id = "GROUP"
|
38
|
+
if restriction.generated_data_criteria.nil?
|
39
|
+
# we pass in restriction.preconditions here rather than children_criteria because we need to be able to create grouping data criteria for and and or preconditions in a tree
|
40
|
+
group_criteria = data_criteria_converter.create_group_data_criteria(restriction.preconditions, "#{type}_CHILDREN", value, parent_id, @@ids.next, "grouping", "temporal")
|
41
|
+
# save the generated grouping criteria so that we can reference it from other locations
|
42
|
+
restriction.generated_data_criteria = group_criteria
|
43
|
+
else
|
44
|
+
# we have already processed this restriction and have a grouping criteria for it. Take the one we have previously generated
|
45
|
+
group_criteria = restriction.generated_data_criteria
|
46
|
+
end
|
47
|
+
temporal_reference = HQMF::TemporalReference.new(type, HQMF::Reference.new(group_criteria.id), value)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
raise "no target for temporal restriction"
|
51
|
+
end
|
52
|
+
restriction.converted=true
|
53
|
+
|
54
|
+
# add temporal reference to data criteria
|
55
|
+
data_criteria.temporal_references << temporal_reference unless data_criteria.has_temporal(temporal_reference)
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def self.apply_summary(data_criteria, precondition, restriction, data_criteria_converter)
|
60
|
+
value = restriction.operator.value
|
61
|
+
type = restriction.operator.type
|
62
|
+
subset_operator = HQMF::SubsetOperator.new(type, value)
|
63
|
+
|
64
|
+
if (restriction.multi_target?)
|
65
|
+
children_criteria = HQMF::DataCriteriaConverter.extract_data_criteria(restriction.preconditions, data_criteria_converter)
|
66
|
+
|
67
|
+
data_criteria = nil
|
68
|
+
if (children_criteria.length == 1)
|
69
|
+
data_criteria = children_criteria[0]
|
70
|
+
# this block used to set the order of operations of values after subset operators.
|
71
|
+
# if data_criteria and !data_criteria.value.nil?
|
72
|
+
# subset_operator.value ||= data_criteria.value
|
73
|
+
# data_criteria.value = nil
|
74
|
+
# end
|
75
|
+
data_criteria.subset_operators ||= []
|
76
|
+
# add subset operator to data criteria
|
77
|
+
data_criteria.subset_operators << subset_operator unless data_criteria.has_subset(subset_operator)
|
78
|
+
else
|
79
|
+
parent_id = "GROUP"
|
80
|
+
|
81
|
+
# this block of code used to be used to pull the value off of a data criteria and shove it onto a subset operator to evaluate MIN: lab result(result > 10) as MIN > 10: lab result
|
82
|
+
# the decision was made by NQF to not support this order of operations based on previous guidance to the measure developers
|
83
|
+
#
|
84
|
+
# unless subset_operator.value
|
85
|
+
# # scalar comparisons are used for MIN>90 etc. The value is on a REFR restriction. We need to store it on the data criteria since the value is processed before the operator is created.
|
86
|
+
# scalar_comparison = nil
|
87
|
+
#
|
88
|
+
# # check to see if we have different values referenced accross the children
|
89
|
+
# # this happens on things like most recent blood pressure reading
|
90
|
+
# multiple_differing_values = false
|
91
|
+
# children_criteria.each do |criteria|
|
92
|
+
# if scalar_comparison.nil?
|
93
|
+
# scalar_comparison = criteria.value
|
94
|
+
# else
|
95
|
+
# if scalar_comparison != criteria.value
|
96
|
+
# multiple_differing_values = true
|
97
|
+
# end
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# # if we have multiple differing values, we want to keep the preconditions from the restriction, and attach them to the precondition
|
102
|
+
# # we can then apply the subset operator to all of the children individually
|
103
|
+
# if (multiple_differing_values)
|
104
|
+
# precondition.preconditions.delete(restriction)
|
105
|
+
# precondition.preconditions.concat(restriction.preconditions)
|
106
|
+
# children_criteria.each do |criteria|
|
107
|
+
# subset_operator = HQMF::SubsetOperator.new(type, value)
|
108
|
+
# subset_operator.value = criteria.value
|
109
|
+
# criteria.value = nil
|
110
|
+
# criteria.subset_operators ||= []
|
111
|
+
# criteria.subset_operators << subset_operator unless criteria.has_subset(subset_operator)
|
112
|
+
# end
|
113
|
+
# restriction.converted=true
|
114
|
+
# # we want to return since we have applied the subset to the children. We no longer want to create a grouping data critiera
|
115
|
+
# return;
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# # all the children have the same value, apply the value to the subset operator
|
119
|
+
# children_criteria.each do |criteria|
|
120
|
+
# if scalar_comparison.nil?
|
121
|
+
# scalar_comparison = criteria.value
|
122
|
+
# else
|
123
|
+
# raise "multiple different scalar comparisons for a grouping data criteria" if scalar_comparison != criteria.value
|
124
|
+
# end
|
125
|
+
# criteria.value = nil
|
126
|
+
# end
|
127
|
+
# subset_operator.value ||= scalar_comparison
|
128
|
+
# end
|
129
|
+
|
130
|
+
if restriction.generated_data_criteria.nil?
|
131
|
+
# we pass in restriction.preconditions here rather than children_criteria because we need to be able to create grouping data criteria for and and or preconditions in a tree
|
132
|
+
data_criteria = data_criteria_converter.create_group_data_criteria(restriction.preconditions, type, value, parent_id, @@ids.next, "grouping", "summary")
|
133
|
+
# save the generated grouping criteria so that we can reference it from other locations
|
134
|
+
restriction.generated_data_criteria = data_criteria
|
135
|
+
else
|
136
|
+
# we have already processed this restriction and have a grouping criteria for it. Take the one we have previously generated
|
137
|
+
data_criteria = restriction.generated_data_criteria
|
138
|
+
end
|
139
|
+
|
140
|
+
data_criteria.subset_operators ||= []
|
141
|
+
# add subset operator to data criteria
|
142
|
+
data_criteria.subset_operators << subset_operator unless data_criteria.has_subset(subset_operator)
|
143
|
+
end
|
144
|
+
precondition.reference = HQMF::Reference.new(data_criteria.id)
|
145
|
+
elsif (restriction.single_target?)
|
146
|
+
subset_operator = HQMF::SubsetOperator.new(type, value)
|
147
|
+
data_criteria.subset_operators ||= []
|
148
|
+
# add subset operator to data criteria
|
149
|
+
data_criteria.subset_operators << subset_operator unless data_criteria.has_subset(subset_operator)
|
150
|
+
end
|
151
|
+
|
152
|
+
restriction.converted=true
|
153
|
+
end
|
154
|
+
|
155
|
+
# Simple class to issue monotonically increasing integer identifiers
|
156
|
+
class Counter
|
157
|
+
def initialize
|
158
|
+
@count = 0
|
159
|
+
end
|
160
|
+
|
161
|
+
def next
|
162
|
+
@count+=1
|
163
|
+
end
|
164
|
+
end
|
165
|
+
@@ids = Counter.new
|
166
|
+
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module HQMF
|
2
|
+
# Class for cleaning up references to specific occurrences in the tree
|
3
|
+
class SpecificOccurrenceConverter
|
4
|
+
|
5
|
+
def initialize(data_criteria_converter)
|
6
|
+
@data_criteria_converter = data_criteria_converter
|
7
|
+
end
|
8
|
+
|
9
|
+
def convert_specific_occurrences(population_criteria)
|
10
|
+
population_criteria.each do |population|
|
11
|
+
walk_up_tree(population.preconditions)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def walk_up_tree(preconditions)
|
16
|
+
preconditions.each do |precondition|
|
17
|
+
if (has_child_comparison(precondition))
|
18
|
+
walk_up_tree(precondition.preconditions)
|
19
|
+
end
|
20
|
+
unless (precondition.comparison?)
|
21
|
+
occurrence_map = get_grouped_occurrences(precondition.preconditions)
|
22
|
+
occurrence_map.each do |key, value|
|
23
|
+
|
24
|
+
##
|
25
|
+
##### figure out negations
|
26
|
+
##
|
27
|
+
|
28
|
+
if (precondition.conjunction_code == 'atLeastOneTrue')
|
29
|
+
|
30
|
+
group = @data_criteria_converter.build_group_data_criteria(value, "#{key}_union", precondition.id, HQMF::DataCriteria::UNION)
|
31
|
+
binding.pry
|
32
|
+
elsif (precondition.conjunction_code == 'allTrue')
|
33
|
+
binding.pry
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_grouped_occurrences(preconditions)
|
42
|
+
|
43
|
+
result = {}
|
44
|
+
preconditions.each do |precondition|
|
45
|
+
if (precondition.preconditions and !precondition.preconditions.empty?)
|
46
|
+
get_grouped_occurrences(precondition.preconditions).each do |key, value|
|
47
|
+
if (result[key])
|
48
|
+
result[key].concat(value)
|
49
|
+
else
|
50
|
+
result[key] = value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
else
|
54
|
+
data_criteria = @data_criteria_converter.v2_data_criteria_by_id[precondition.reference.id]
|
55
|
+
if is_specific_occurrence(data_criteria)
|
56
|
+
binding.pry
|
57
|
+
result[data_criteria.source_data_criteria] ||= []
|
58
|
+
result[data_criteria.source_data_criteria] << data_criteria
|
59
|
+
preconditions.delete(precondition)
|
60
|
+
binding.pry
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
result
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def is_specific_occurrence(data_criteria)
|
69
|
+
!data_criteria.specific_occurrence.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
def has_child_comparison(node)
|
73
|
+
value = false
|
74
|
+
node.preconditions.each do |precondition|
|
75
|
+
if (precondition.comparison?)
|
76
|
+
value ||= true
|
77
|
+
elsif precondition.has_preconditions?
|
78
|
+
value ||= has_child_comparison(precondition)
|
79
|
+
end
|
80
|
+
end if node.preconditions
|
81
|
+
value
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module HQMF
|
2
|
+
# Class for cleaning up references to specific occurrences in the tree
|
3
|
+
class SpecificOccurrenceConverter
|
4
|
+
|
5
|
+
def initialize(data_criteria_converter)
|
6
|
+
@data_criteria_converter = data_criteria_converter
|
7
|
+
end
|
8
|
+
|
9
|
+
def convert_specific_occurrences(population_criteria)
|
10
|
+
create_precondition_map(population_criteria)
|
11
|
+
|
12
|
+
@specific_occurrence_key_mapper = {}
|
13
|
+
|
14
|
+
specific_occurrences = {}
|
15
|
+
@data_criteria_converter.v2_data_criteria.each do |data_criteria|
|
16
|
+
if (is_specific_occurrence(data_criteria))
|
17
|
+
if (data_criteria.respond_to? :precondition_id and @precondition_map[data_criteria.precondition_id])
|
18
|
+
specific_occurrences[data_criteria.source_data_criteria] ||= []
|
19
|
+
specific_occurrences[data_criteria.source_data_criteria] << data_criteria
|
20
|
+
elsif (data_criteria.children_criteria or data_criteria.subset_operators or data_criteria.temporal_references)
|
21
|
+
binding.pry
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
specific_occurrences.each do |key, occurrences|
|
26
|
+
|
27
|
+
occurrences.sort! do |left, right|
|
28
|
+
@precondition_map[right.precondition_id].depth <=> @precondition_map[left.precondition_id].depth
|
29
|
+
end
|
30
|
+
|
31
|
+
occurrences.each_cons(2) do |pair|
|
32
|
+
parent = get_common_parent(@precondition_map[pair[0].precondition_id], @precondition_map[pair[1].precondition_id])
|
33
|
+
binding.pry
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_common_parent(left, right)
|
40
|
+
return left if left == right
|
41
|
+
get_common_parent(@precondition_parent_map[left.id], @precondition_parent_map[right.id])
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_precondition_map(population_criteria)
|
45
|
+
@precondition_map = {}
|
46
|
+
@precondition_parent_map = {}
|
47
|
+
depth = 0
|
48
|
+
population_criteria.each do |population|
|
49
|
+
recurse_precondition_map(population, depth)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def recurse_precondition_map(node, depth)
|
54
|
+
node.preconditions.each do |precondition|
|
55
|
+
precondition.depth = depth
|
56
|
+
@precondition_map[precondition.id] = precondition
|
57
|
+
@precondition_parent_map[precondition.id] = node
|
58
|
+
if precondition.has_preconditions?
|
59
|
+
depth += 1
|
60
|
+
recurse_precondition_map(precondition, depth)
|
61
|
+
end
|
62
|
+
end if node.preconditions
|
63
|
+
end
|
64
|
+
|
65
|
+
def is_specific_occurrence(data_criteria)
|
66
|
+
!data_criteria.specific_occurrence.nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module HQMF
|
2
|
+
class Parser
|
3
|
+
|
4
|
+
HQMF_VERSION_1 = "1.0"
|
5
|
+
HQMF_VERSION_2 = "2.0"
|
6
|
+
|
7
|
+
def self.parse(hqmf_contents, version, codes = nil)
|
8
|
+
|
9
|
+
|
10
|
+
case version
|
11
|
+
when HQMF_VERSION_1
|
12
|
+
puts("\tCodes not passed in, cannot backfill properties like gender") unless codes
|
13
|
+
HQMF::DocumentConverter.convert(HQMF1::Document.new(hqmf_contents).to_json, codes)
|
14
|
+
when HQMF_VERSION_2
|
15
|
+
HQMF2::Document.new(hqmf_contents).to_model
|
16
|
+
else
|
17
|
+
raise "Unsupported HQMF version specified: #{version}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'zip/zipfilesystem'
|
2
|
+
require 'spreadsheet'
|
3
|
+
require 'google_spreadsheet'
|
4
|
+
require 'roo'
|
5
|
+
require 'iconv'
|
6
|
+
|
7
|
+
module HQMF
|
8
|
+
module ValueSet
|
9
|
+
class Parser
|
10
|
+
|
11
|
+
GROUP_CODE_SET = "GROUPING"
|
12
|
+
|
13
|
+
ORGANIZATION_TITLE = "Value Set Developer"
|
14
|
+
OID_TITLE = "Value Set OID"
|
15
|
+
CONCEPT_TITLE = "Value Set Name"
|
16
|
+
CATEGORY_TITLE = "QDM Category"
|
17
|
+
CODE_SET_TITLE ="Code System"
|
18
|
+
VERSION_TITLE = "Code System Version"
|
19
|
+
CODE_TITLE = "Code"
|
20
|
+
DESCRIPTION_TITLE = "Descriptor"
|
21
|
+
|
22
|
+
CODE_SYSTEM_NORMALIZER = {
|
23
|
+
'ICD-9'=>'ICD-9-CM',
|
24
|
+
'ICD-10'=>'ICD-10-CM',
|
25
|
+
'HL7 (2.16.840.1.113883.5.1)'=>'HL7'
|
26
|
+
}
|
27
|
+
IGNORED_CODE_SYSTEM_NAMES = ['Grouping', 'GROUPING' ,'HL7', "Administrative Sex"]
|
28
|
+
|
29
|
+
def initialize()
|
30
|
+
end
|
31
|
+
|
32
|
+
# import an excel matrix array into mongo
|
33
|
+
def parse(file, options={})
|
34
|
+
sheet_array = file_to_array(file, options)
|
35
|
+
by_oid_ungrouped = cells_to_hashs_by_oid(sheet_array)
|
36
|
+
collapse_groups(by_oid_ungrouped)
|
37
|
+
end
|
38
|
+
|
39
|
+
def collapse_groups(by_oid_ungrouped)
|
40
|
+
|
41
|
+
final = []
|
42
|
+
|
43
|
+
# select the grouped code sets and fill in the children... also remove the children that are a
|
44
|
+
# member of a group. We remove the children so that we can create parent groups for the orphans
|
45
|
+
(by_oid_ungrouped.select {|key,value| value["code_set"].upcase == GROUP_CODE_SET}).each do |key, value|
|
46
|
+
# remove the group so that it is not in the orphan list
|
47
|
+
by_oid_ungrouped.delete(value["oid"])
|
48
|
+
codes = []
|
49
|
+
value["codes"].each do |child_oid|
|
50
|
+
# codes << by_oid_ungrouped.delete(child_oid)
|
51
|
+
# do not delete the children of a group. These may be referenced by other groups or directly by the measure
|
52
|
+
code = by_oid_ungrouped[child_oid]
|
53
|
+
puts "\tcode could not be found: #{child_oid}" unless code
|
54
|
+
codes << code if code
|
55
|
+
# for hierarchies we need to probably have codes be a hash that we select from if we don't find the
|
56
|
+
# element in by_oid_ungrouped we may need to look for it in final
|
57
|
+
end
|
58
|
+
value["code_sets"] = codes
|
59
|
+
value.delete("codes")
|
60
|
+
value.delete("code_set")
|
61
|
+
final << value
|
62
|
+
end
|
63
|
+
|
64
|
+
# fill out the orphans
|
65
|
+
by_oid_ungrouped.each do |key, orphan|
|
66
|
+
final << adopt_orphan(orphan)
|
67
|
+
end
|
68
|
+
|
69
|
+
deleted = []
|
70
|
+
final.delete_if {|x| to_delete = x['code_sets'].nil? || x['code_sets'].empty?; deleted << x if to_delete; to_delete }
|
71
|
+
deleted.each do |value|
|
72
|
+
puts "\tDeleted value set with no code sets: #{value['oid']}"
|
73
|
+
end
|
74
|
+
final
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
def adopt_orphan(orphan)
|
79
|
+
parent = orphan.dup
|
80
|
+
parent["code_sets"] = [orphan]
|
81
|
+
parent.delete("codes")
|
82
|
+
parent.delete("code_set")
|
83
|
+
parent
|
84
|
+
end
|
85
|
+
|
86
|
+
# take an excel matrix array and turn it into an array of db models
|
87
|
+
def cells_to_hashs_by_oid(array)
|
88
|
+
a = Array.new(array) # new variable for reentrant
|
89
|
+
headers = a.shift.map {|i| i.to_s } # because of this shift
|
90
|
+
string_data = a.map {|row| row.map {|cell| cell.to_s } }
|
91
|
+
array_of_hashes = string_data.map {|row| Hash[*headers.zip(row).flatten] }
|
92
|
+
|
93
|
+
by_oid = {}
|
94
|
+
array_of_hashes.each do |row|
|
95
|
+
entry = convert_row(row)
|
96
|
+
|
97
|
+
existing = by_oid[entry["oid"]]
|
98
|
+
if (existing)
|
99
|
+
existing["codes"].concat(entry["codes"])
|
100
|
+
else
|
101
|
+
by_oid[entry["oid"]] = entry
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
by_oid
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.get_format(file_path)
|
109
|
+
if file_path =~ /xls$/
|
110
|
+
:xls
|
111
|
+
elsif file_path =~ /xlsx$/
|
112
|
+
:xlsx
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def convert_row(row)
|
119
|
+
# Value Set Developer
|
120
|
+
# Value Set OID
|
121
|
+
# Value Set Name
|
122
|
+
# QDM Category
|
123
|
+
# Code System
|
124
|
+
# Code System Version
|
125
|
+
# Code
|
126
|
+
# Descriptor
|
127
|
+
value = {
|
128
|
+
"key" => normalize_names(row[CATEGORY_TITLE],row[CONCEPT_TITLE]),
|
129
|
+
"organization" => row[ORGANIZATION_TITLE],
|
130
|
+
"oid" => row[OID_TITLE].strip.gsub(/[^0-9\.]/i, ''),
|
131
|
+
"concept" => normalize_names(row[CONCEPT_TITLE]),
|
132
|
+
"category" => normalize_names(row[CATEGORY_TITLE]),
|
133
|
+
"code_set" => normalize_code_system(row[CODE_SET_TITLE]),
|
134
|
+
"version" => row[VERSION_TITLE],
|
135
|
+
"codes" => extract_code(row[CODE_TITLE], row[CODE_SET_TITLE]),
|
136
|
+
"description" => row[DESCRIPTION_TITLE]
|
137
|
+
}
|
138
|
+
value['codes'].map! {|code| code.strip.gsub(/[^0-9\.]/i, '')} if (value['code_set'].upcase == GROUP_CODE_SET)
|
139
|
+
value
|
140
|
+
end
|
141
|
+
|
142
|
+
# Break all the supplied strings into separate words and return the resulting list as a
|
143
|
+
# new string with each word separated with '_'
|
144
|
+
def normalize_names(*components)
|
145
|
+
name = []
|
146
|
+
components.each do |component|
|
147
|
+
name.concat component.gsub(/\W/,' ').split.collect { |word| word.strip.downcase }
|
148
|
+
end
|
149
|
+
name.join '_'
|
150
|
+
end
|
151
|
+
|
152
|
+
def normalize_code_system(code_system_name)
|
153
|
+
code_system_name = CODE_SYSTEM_NORMALIZER[code_system_name] if CODE_SYSTEM_NORMALIZER[code_system_name]
|
154
|
+
return code_system_name if IGNORED_CODE_SYSTEM_NAMES.include? code_system_name
|
155
|
+
oid = HealthDataStandards::Util::CodeSystemHelper.oid_for_code_system(code_system_name)
|
156
|
+
puts "\tbad code system name: #{code_system_name}" unless oid
|
157
|
+
code_system_name
|
158
|
+
end
|
159
|
+
|
160
|
+
def extract_code(code, set)
|
161
|
+
|
162
|
+
code.strip!
|
163
|
+
if set=='CPT' && code.include?('-')
|
164
|
+
eval(code.strip.gsub('-','..')).to_a.collect { |i| i.to_s }
|
165
|
+
else
|
166
|
+
[code]
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
def file_to_array(file_path, options)
|
172
|
+
defaults = {
|
173
|
+
:columns => 2, # range of import
|
174
|
+
:sheet => 1 # only one sheet at a time can be worked on
|
175
|
+
}
|
176
|
+
options = defaults.merge(options)
|
177
|
+
|
178
|
+
book = book_by_format(file_path, options[:format])
|
179
|
+
book.default_sheet=book.sheets[options[:sheet]]
|
180
|
+
|
181
|
+
# catch double byte encoding problems in spreadsheet files
|
182
|
+
# Encoding::InvalidByteSequenceError: "\x9E\xDE" on UTF-16LE
|
183
|
+
begin
|
184
|
+
book.to_matrix.to_a
|
185
|
+
rescue Encoding::InvalidByteSequenceError => e
|
186
|
+
raise "Spreadsheet encoding problem: #{e}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def book_by_format(file_path, format)
|
191
|
+
format = HQMF::ValueSet::Parser.get_format(file_path) unless format
|
192
|
+
|
193
|
+
if format == :xls
|
194
|
+
book = Excel.new(file_path, nil, :ignore)
|
195
|
+
elsif format == :xlsx
|
196
|
+
book = Excelx.new(file_path, nil, :ignore)
|
197
|
+
else
|
198
|
+
raise "File does not end in .xls or .xlsx"
|
199
|
+
end
|
200
|
+
book
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|