ez7gen 1.0.1

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 (90) hide show
  1. checksums.yaml +7 -0
  2. data/bin/ez7gen +6 -0
  3. data/lib/ez7gen.rb +23 -0
  4. data/lib/ez7gen/config/schema/2.4/2.4.HL7.xml +13904 -0
  5. data/lib/ez7gen/config/schema/2.4/VAZ2.4.HL7.xml +3085 -0
  6. data/lib/ez7gen/config/schema/2.4/added/coded-tables.xml +730 -0
  7. data/lib/ez7gen/config/schema/2.4/rules/2.4.HL7.yml +4 -0
  8. data/lib/ez7gen/config/schema/2.4/rules/VAZ2.4.HL7.yml +6 -0
  9. data/lib/ez7gen/config/schema/2.5/2.5.HL7.xml +10008 -0
  10. data/lib/ez7gen/config/schema/2.5/VAZ2.5.HL7.xml +7 -0
  11. data/lib/ez7gen/config/schema/2.5/added/coded-tables.xml +549 -0
  12. data/lib/ez7gen/config/schema/readme.txt +0 -0
  13. data/lib/ez7gen/config/templates/2.4/eiv table update-mfn_m01 20151201.xml +416 -0
  14. data/lib/ez7gen/config/templates/2.4/eiv table update-mfn_y01.xml +416 -0
  15. data/lib/ez7gen/config/templates/2.4/eiv-ec-MFN_X01_reg request 20160126.xml +659 -0
  16. data/lib/ez7gen/config/templates/2.4/examples/ADT_A60.txt +69 -0
  17. data/lib/ez7gen/config/templates/2.4/examples/eiv table update-mfn_m01 20151201.txt +21 -0
  18. data/lib/ez7gen/config/templates/2.4/examples/mhvsm_dss_units-query_qbp_q13-qbp_q13.txt +26 -0
  19. data/lib/ez7gen/config/templates/2.4/examples/mhvsm_ecs_procedures_query_qbp_q13-qbp_q13.txt +26 -0
  20. data/lib/ez7gen/config/templates/2.4/examples/mhvsm_patient eligibility_response-rsp_k11-080714.txt +44 -0
  21. data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_diagnosis_query_qbp_q11-qbp_q11.txt +21 -0
  22. data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_diagnosis_response_rsp_k11-rsp_k11.txt +42 -0
  23. data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_dss_units_response_rtb_k13-rtb_k13.txt +49 -0
  24. data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_ecs_filer_request_dft_p03-dft_p03-080714.txt +31 -0
  25. data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_ecs_filer_response_ack_p03-ack_p03.txt +21 -0
  26. data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_esc_procedures_response_rtb_k13-rtb_k13.txt +40 -0
  27. data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_patient_eclass_query_qbp_q11-qbp_q11.txt +21 -0
  28. data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_patient_problems_query_qbp_q11-qbp_q11.txt +21 -0
  29. data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_patinet_problems_response_rsp_k11-rsp_k11.txt +33 -0
  30. data/lib/ez7gen/config/templates/2.4/examples/orur01rvbecv2.txt +31 -0
  31. data/lib/ez7gen/config/templates/2.4/examples/sqwm vitals-oru_ro1.txt +52 -0
  32. data/lib/ez7gen/config/templates/2.4/examples/vista sqwm-adt_a60.txt +40 -0
  33. data/lib/ez7gen/config/templates/2.4/mhvsm_dss_units_query_qbp_q13-qbp_q13.xml +312 -0
  34. data/lib/ez7gen/config/templates/2.4/mhvsm_ecs_procedures_query_qbp_q13-qbp_q13.xml +314 -0
  35. data/lib/ez7gen/config/templates/2.4/mhvsm_patient eligibility_response-rsp_k11-080714.xml +640 -0
  36. data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_diagnosis_query_qbp_q11-qbp_q11.xml +284 -0
  37. data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_diagnosis_response_rsp_k11-rsp_k11-rsp_k11.xml +563 -0
  38. data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_dss_units_response_rtb_k13-rtb_k13-rtb_k13.xml +365 -0
  39. data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_ecs_filer_request_dft_p03-dft_p03-080714.xml +2172 -0
  40. data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_ecs_filer_response_ack_p03-ack_p03.xml +269 -0
  41. data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_ecs_procedures_response_rtb_k13-rtb_k13-rtb_k13.xml +354 -0
  42. data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_patient_eclass_query_qbp_q11-qbp_q11.xml +284 -0
  43. data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_patient_problems_query_qbp_q11-qbp_q11.xml +282 -0
  44. data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_patient_problems_response_rsp_k11-rsp_k11-rsp_k11.xml +565 -0
  45. data/lib/ez7gen/config/templates/2.4/orur01rvbecv2.xml +1529 -0
  46. data/lib/ez7gen/config/templates/2.4/sqwm vitals-oru_r01.xml +2975 -0
  47. data/lib/ez7gen/config/templates/2.4/vista sqwm-adt_a60.xml +1360 -0
  48. data/lib/ez7gen/message_factory.rb +142 -0
  49. data/lib/ez7gen/msg_error_handler.rb +33 -0
  50. data/lib/ez7gen/profile_parser.rb +321 -0
  51. data/lib/ez7gen/resources/properties-with-comments.yml +51 -0
  52. data/lib/ez7gen/resources/properties.yml +325 -0
  53. data/lib/ez7gen/service/2.4/dynamic_field_generator.rb +45 -0
  54. data/lib/ez7gen/service/2.4/field_generator.rb +1586 -0
  55. data/lib/ez7gen/service/2.5/field_generator.rb +75 -0
  56. data/lib/ez7gen/service/base_field_generator.rb +451 -0
  57. data/lib/ez7gen/service/segment_generator.rb +218 -0
  58. data/lib/ez7gen/service/segment_picker.rb +147 -0
  59. data/lib/ez7gen/service/template_generator.rb +213 -0
  60. data/lib/ez7gen/service/type_aware_field_generator.rb +1583 -0
  61. data/lib/ez7gen/service/utils.rb +75 -0
  62. data/lib/ez7gen/structure_parser.rb +331 -0
  63. data/lib/ez7gen/version.rb +38 -0
  64. data/test/Additional Tables with values_v1.1.txt +1653 -0
  65. data/test/added_shema_test.rb +143 -0
  66. data/test/app-tmp.rb +225 -0
  67. data/test/at.txt +1 -0
  68. data/test/backburner.zip +0 -0
  69. data/test/codes.txt +262 -0
  70. data/test/codes1.txt +1240 -0
  71. data/test/data_types_exploration_test.rb +213 -0
  72. data/test/dynamic_field_generated_test.rb +292 -0
  73. data/test/message_factory_24_custom_test.rb +648 -0
  74. data/test/message_factory_25_test.rb +50 -0
  75. data/test/message_factory_adm_test.rb +558 -0
  76. data/test/message_factory_gen_test.rb +63 -0
  77. data/test/message_factory_lab_test.rb +107 -0
  78. data/test/message_factory_pharm_test.rb +121 -0
  79. data/test/message_factory_template_24_test.rb +730 -0
  80. data/test/message_factory_test.rb +220 -0
  81. data/test/msg_error_handler_test.rb +59 -0
  82. data/test/profile_parser_test.rb +542 -0
  83. data/test/quick_run.rb +880 -0
  84. data/test/segment_generator_test.rb +656 -0
  85. data/test/segment_picker_test.rb +279 -0
  86. data/test/structrure_parser_test.rb +355 -0
  87. data/test/template_generator_test.rb +164 -0
  88. data/test/type_aware_field_generator_test.rb +582 -0
  89. data/test/utils_test.rb +97 -0
  90. metadata +215 -0
@@ -0,0 +1,218 @@
1
+ require 'ruby-hl7'
2
+ require 'date'
3
+ require 'benchmark'
4
+
5
+ # require_relative 'type_aware_field_generator'
6
+ require_relative '../../../lib/ez7gen/structure_parser'
7
+ require_relative 'utils'
8
+
9
+ class SegmentGenerator
10
+ include Utils
11
+
12
+ @@maxReps = 2
13
+ @@random = Random.new
14
+ @@BASE_VER={'2.4'=>'2.4','vaz2.4'=>'2.4'}
15
+ @@SET_ID_PIECE = 1
16
+
17
+
18
+ # TODO: do I need accessors for version and event? refactor.
19
+ attr_accessor :version; :event;
20
+
21
+ # constructor
22
+ def initialize(version, event, pp)
23
+ @version = version
24
+ @event = event
25
+
26
+ #If there are multiple profile parsers, instantiate a generators for each
27
+ @fieldGenerators = {}
28
+ pp.each{|profileName, parser|
29
+ # helper parser for lookup in the other schema
30
+ # when generating segments for custom (not base) ex VAZ2.4 the field generator will have to look in both schemas
31
+ # to resolve types and coded tables value.
32
+ # we will assign the other schema parser as a helper parser
33
+ helper_parser = pp.select{|key, value| key != profileName}
34
+ helper_parser = (helper_parser.empty?) ? nil: helper_parser.values.first
35
+
36
+ # starting with 2.5 use schema and version specific type generator.
37
+ @fieldGenerators[profileName] = get_field_generator(parser, helper_parser)
38
+ }
39
+ # p @fieldGenerators
40
+ end
41
+
42
+ def get_field_generator(parser, helper_parser)
43
+ gen = nil
44
+ begin
45
+ require_relative "../../ez7gen/service/#{version}/field_generator"
46
+ gen = FieldGenerator.new( parser, helper_parser)
47
+ rescue => e
48
+ # p e
49
+ $log.error("#{self.class.to_s}:#{__method__.to_s}") { e.message }
50
+ # @fieldGenerators[profileName] = TypeAwareFieldGenerator.new( parser, helper_parser)
51
+ end
52
+ return gen
53
+ end
54
+
55
+ # initialize msh segment
56
+ def init_msh
57
+ # create a MSH segment
58
+ msh = HL7::Message::Segment::MSH.new
59
+
60
+ #pick a field generator
61
+ fieldGenerator = @fieldGenerators['primary']
62
+
63
+ msh.enc_chars ='^~\&'
64
+ # msh.sending_app = fieldGenerator.HD({:codetable =>'361', :required =>'R'})
65
+ msh.sending_app = fieldGenerator.dt('HD',{:codetable =>'361', :required =>'R'})
66
+ # msh.sending_facility = fieldGenerator.HD({:codetable => '362', :required =>'R'})
67
+ msh.sending_facility = fieldGenerator.dt('HD',{:codetable => '362', :required =>'R'})
68
+ msh.recv_app = fieldGenerator.dt('HD',{:codetable => '361', :required =>'R'})
69
+ msh.recv_facility = fieldGenerator.dt('HD',{:codetable => '362', :required =>'R'})
70
+ msh.processing_id = 'P'#@fieldGenerators['primary'].ID({},true)
71
+ #Per Galina, set version to 2.4 for all of vaz
72
+ # msh.version_id = @@BASE_VER[@version]
73
+ msh.version_id = @version
74
+ msh.security = fieldGenerator.dt('ID',{:required =>'O'})
75
+
76
+ # Per Galina's requirement, fix for validation failure.
77
+ # MSH.9.3 needs to be populated with the correct Message Structure values for those messages
78
+ # that are the “copies” of the “original” messages.
79
+ structType = fieldGenerator.pp.get_message_structure(@event)
80
+ msh.message_type = @event.sub('_','^')<<'^'<<structType
81
+
82
+ msh.time = DateTime.now.strftime('%Y%m%d%H%M%S.%L')
83
+ msh.message_control_id = fieldGenerator.dt('ID',{:required =>'R'})
84
+ msh.seq = fieldGenerator.dt('ID',{:required=>'O'})
85
+ msh.continue_ptr = fieldGenerator.dt('ID',{:required=>'O'})
86
+ msh.accept_ack_type = fieldGenerator.dt('ID',{:required=>'R', :codetable=>'155'})
87
+ msh.app_ack_type = fieldGenerator.dt('ID',{:required=>'R', :codetable=>'155'})
88
+ msh.country_code = fieldGenerator.dt('ID',{:required=>'R', :codetable=>'399'})
89
+ # msh.charset = @fieldGenerators['primary'].ID({:required=>'R', :codetable=>'211'})
90
+ msh.charset = 'ASCII' # default value from codetable, change causes problems in validating messages in Ensemble
91
+ #Table 296 Primary Language has no suggested values. The field will be populated with values from the Primary Language table in the properties file. Example value: EN^English
92
+ msh.principal_language_of_message ='EN^English'
93
+ msh.alternate_character_set_handling_scheme = fieldGenerator.dt('ID',{:required=>'O', :codetable=>'356'})
94
+ # 21 Conformance Statement ID
95
+ msh.e20 = fieldGenerator.dt('ID',{:required=>'O', :codetable=>'449'})
96
+
97
+ return msh
98
+ end
99
+
100
+ # refactoring
101
+ def generate(message, segment, parsers, isGroup=false)
102
+ if(segment.kind_of?(Array))
103
+ # handle group
104
+ generate_group(message, segment, parsers)
105
+ else
106
+ # build_segment
107
+ choiceParser = parsers[get_type_by_name(segment)]
108
+ attributes = choiceParser.get_segment_structure(get_name_without_base(segment))
109
+ generate_segment_in_context(message, segment, attributes, isGroup)
110
+
111
+ end
112
+ end
113
+
114
+ # generate a group of segments in test message segment using metadata
115
+ def generate_group( message, group, parsers)
116
+ # generate each segment in the group
117
+ totalReps = (group.instance_of?(RepeatingGroup))? (1..@@maxReps).to_a.sample: 1
118
+ totalReps.times do |i|
119
+ group.each{|seg| generate(message, seg, parsers, true) }
120
+ end
121
+
122
+ return message
123
+ end
124
+
125
+ # end
126
+
127
+ # generate test message segment using metadata
128
+ def generate_segment_in_context(message, segment, attributes, isGroup=false)
129
+ isRep = is_segment_repeating?(segment)
130
+ segmentName = get_segment_name(segment)
131
+
132
+ # decide if segment needs to repeat and how many times
133
+ # totalReps = (isRep)? @@random.rand(1.. @@maxReps) : 1 # between 1 and maxReps
134
+ totalReps = (isRep)? (1..@@maxReps).to_a.sample: 1
135
+
136
+ totalReps.times do |i|
137
+ # seg = (isRep)?message."get$segmentName"(i) :message."get$segmentName"()
138
+ #groupId = (totalReps>1)?i+1 :((isGroup)?1:nil)
139
+ sharedGroupId = (isGroup)? i+1: nil
140
+ message << generate_segment(segmentName, attributes, sharedGroupId)
141
+ end
142
+
143
+ return message
144
+ end
145
+
146
+
147
+ # #generate_segment_in_context test message using
148
+ # def generateSegmentFields( segment, attributes)
149
+ # segmentName = Utils.get_segment_name(segment)
150
+ # generate_segment(segmentName, attributes)
151
+ # end
152
+
153
+ def is_segment_repeating?(segment)
154
+ segment.include?("~{")
155
+ end
156
+
157
+ # generate a segment using Ensemble schema
158
+ def generate_segment(segmentName, attributes, idx=nil)
159
+ elements = generate_segment_elements(segmentName, attributes)
160
+ # overrite ids for sequential repeating segments
161
+ elements[@@SET_ID_PIECE] = handle_set_id(segmentName, attributes, idx) || elements[@@SET_ID_PIECE]
162
+
163
+ #generate segment using elements
164
+ HL7::Message::Segment::Default.new(elements)
165
+ end
166
+
167
+ def handle_set_id( segmentName, attributes, idx)
168
+
169
+ #set-id field sometimes set to specific non numeric value, keep it, otherwise override if needed
170
+ is_from_codetable = attributes.find { |p| p[:piece] == @@SET_ID_PIECE.to_s }[:codetable]
171
+ if(!is_from_codetable) # ignore any value that generated using codetable
172
+ #Set ID field in PID.1, AL1.1, DG1.1 etc. should have number 1 for the first occurrence of the segment.
173
+ (idx) ? idx.to_s : (['PID', 'AL1', 'DG1'].include?(segmentName))? '1' :nil
174
+ else
175
+ nil
176
+ end
177
+
178
+ end
179
+
180
+ # use attributes to generate contents of a specific segment
181
+ def generate_segment_elements(segmentName, attributes)
182
+
183
+ fields =[]
184
+ total = attributes.size()
185
+ # type = get_type_by_name(segmentName)
186
+ # generate segment attributes
187
+ total.times do |i|
188
+ fields << add_field(attributes[i])
189
+ end
190
+
191
+ # add segment name to the beginning of the array
192
+ fields.unshift(get_name_without_base(segmentName))
193
+ end
194
+
195
+ #adds a generated field based on data type
196
+ def add_field(attributes)
197
+
198
+ type = get_type_by_name(attributes[:datatype])
199
+
200
+ if(blank?(type)) #safe handle missing data typetype
201
+ attributes[:datatype] = 'ID'
202
+ type = get_type_by_name(attributes[:datatype])
203
+ end
204
+
205
+ fieldGenerator= @fieldGenerators[type]
206
+ data_type = get_name_without_base(attributes[:datatype])
207
+
208
+ # puts Utils.blank?(dt)?'~~~~~~~~~> data type is missing': dt
209
+ if(['CK'].include?(data_type))
210
+ return nil
211
+ else
212
+ # fld = blank?(dt)?nil :fieldGenerator.method(dt).call(attributes)
213
+ fld = blank?(data_type)?nil :fieldGenerator.dt(data_type, attributes)
214
+ end
215
+
216
+ end
217
+
218
+ end
@@ -0,0 +1,147 @@
1
+ require_relative 'utils'
2
+ require_relative '../../ez7gen/structure_parser'
3
+
4
+ class SegmentPicker
5
+ include Utils
6
+
7
+ attr_accessor :encodedSegments
8
+ attr_accessor :profile
9
+
10
+ # load 50 percent of optional segments
11
+ @@LOAD_FACTOR = 0.5
12
+ @@MSH_SEGMENTS = ['MSH', "#{BASE_INDICATOR}MSH"]
13
+ #@@MSH_SEGMENTS = ['MSH', "base:MSH"]
14
+
15
+ @@random = Random.new
16
+
17
+ def initialize(profile, encodedSegments, loadFactor=nil)
18
+ @profile = profile
19
+ @encodedSegments = encodedSegments
20
+ #refactoring
21
+ @candidates =[]
22
+
23
+ @loadFactor = loadFactor
24
+ @loadFactor||=@@LOAD_FACTOR # set to default if not specified or set to nil
25
+ end
26
+
27
+ #refactoring
28
+ # Get list of segments for test message generation.
29
+ # MSH is populated with quick generation, skip it here.
30
+ def pick_segments_to_build()
31
+ idxs = pick_segment_idx_to_build
32
+ segmentCandidates = build_segments_for_indexes(idxs)
33
+ return segmentCandidates - @@MSH_SEGMENTS
34
+ end
35
+
36
+ # Turn indexes to segments
37
+ def build_segments_for_indexes(idxs)
38
+ idxs.map do |it|
39
+ if(is_number?(@profile[it]))
40
+ idx = @profile[it].to_i
41
+ @encodedSegments[idx]
42
+ else
43
+ @profile[it]
44
+ end
45
+ end
46
+ end
47
+
48
+ def pick_segment_idx_to_build
49
+ reqIdxs= get_required_segment_idxs()
50
+ #profile indexes - reqired = optional?
51
+ optIdxs = get_optional_segment_idxs(reqIdxs)
52
+ (reqIdxs+optIdxs).sort.uniq
53
+ end
54
+
55
+ def get_optional_segment_idxs(regiredIdxs)
56
+ # range of indexes
57
+ allIdxs = (0...@profile.size).to_a
58
+ optIdxs = allIdxs- regiredIdxs
59
+ count = get_load_candidates_count(optIdxs.size())
60
+ optIdxs.sample(count)
61
+ end
62
+
63
+ # get segments that will always be build, include z segments
64
+ def get_required_segment_idxs()
65
+ # profile already has all required segments identified
66
+ rs = @profile.each_index.select{|it| is_required1?(@profile[it])}
67
+ # promote z segments to required, and add them as required, keeping their index
68
+ zs = @profile.each_index.select{|it| is_z1?(@profile[it])}
69
+ # return indexes
70
+ (rs+zs).sort.uniq
71
+ end
72
+
73
+ #end refactoring
74
+
75
+ # get segments that will always be build, include z segments
76
+ def get_required_segments()
77
+ # profile already has all required segments identified
78
+ # promote z segments to required, and add them as required, keeping their index
79
+ zs = @encodedSegments.select{|it| is_z?(it)}
80
+
81
+ #promote z to required, replace it's placeholder in profile with the value of the segment
82
+ # adjust optional segments, clear the value, but do not delete to preserve the indexes
83
+ zs.each{|it| idx = @encodedSegments.index(it); @profile[@profile.index(idx)] = it; @encodedSegments[idx] = nil}
84
+
85
+ #reset encoded segments
86
+ @encodedSegments.delete_if{|it| it == nil}
87
+
88
+ # Make a copy of profile and set to nil all optional segments, indexes into encoded segments array
89
+ # required = []
90
+ # @profile.each{|it| required << Utils.num_to_nil(it)}
91
+ #
92
+ return @profile.select{|it| is_required?(it)}
93
+ end
94
+
95
+ # calculate number of segments based on load factor
96
+ def get_load_candidates_count(total)
97
+ (total*@loadFactor).ceil #round it up
98
+ end
99
+
100
+ # check is segment is required
101
+ def is_required1?(encoded)
102
+ check = false
103
+ #segment not encoded
104
+ if(!is_number?(encoded))
105
+ check = true
106
+ else
107
+ # look at encoded segment for the index
108
+ seg = @encodedSegments[encoded.to_i]
109
+ # Required segments left not encoded as strings, optional and groups encoded - indexes of encoded segments
110
+ if(seg.instance_of?(RepeatingGroup))
111
+ check = true
112
+ elsif(seg.instance_of?(String))
113
+ check = (seg[0] == '{' ) # signifies repeating segment
114
+ end
115
+ end
116
+
117
+ return check
118
+ end
119
+
120
+ def is_required?(encoded)
121
+ # Required segments left not encoded as strings, optional and groups encoded as numbers
122
+ !is_number?(encoded)
123
+ end
124
+
125
+
126
+ def is_z?(segment)
127
+ segment=~/\~Z/
128
+ end
129
+
130
+ #refactoring
131
+ def is_z1?(encoded)
132
+ segment = ''
133
+ if(is_number?(encoded))
134
+ # look at encoded segment for the index
135
+ segment = @encodedSegments[encoded.to_i]
136
+ #if segment happen to be a group, flatten it into string
137
+ if(segment.kind_of?(Array))
138
+ segment = segment.flatten().to_s
139
+ end
140
+ else
141
+ #segment was not encoded to an index, use it as is
142
+ segment = encoded
143
+ end
144
+
145
+ (segment =~ /\~Z/)? true: false
146
+ end
147
+ end
@@ -0,0 +1,213 @@
1
+ require_relative 'type_aware_field_generator.rb'
2
+ require_relative 'utils'
3
+
4
+ class TemplateGenerator
5
+ include Utils
6
+ #TODO: refactor in one place
7
+ @@HAT = '^' # Component separator, aka hat
8
+ @@SUB ='&' # Subcomponent separator
9
+
10
+ # xml tags used in MWB schemas
11
+ DATAVALUES = 'DataValues'
12
+ COMPONENT = 'Component'
13
+ SUBCOMPONENT = 'SubComponent'
14
+
15
+ # use xml tags as symbols for name of collections
16
+ COMP = COMPONENT.downcase.intern
17
+ SUB = SUBCOMPONENT.downcase.intern
18
+
19
+ # list of usages to be picked up, other ignored
20
+ # @@USAGES = ['R','RE']
21
+ USAGES_REQ = ['R']
22
+ USAGES_OPT = ['RE']
23
+
24
+ # initialise template generator with the path to template xml (MWB) and parcers
25
+ def initialize(tempalte_path, pp)
26
+
27
+ # parse template TODO: refactor if not needed on class level move to 'build_template_metadata'
28
+ text = File.path(tempalte_path)
29
+ @xml = Ox.parse(IO.read(text))
30
+
31
+ #If there are multiple profile parsers, instantiate a generators for each
32
+ @fieldGenerators = {}
33
+ # helper parser for lookup in the other schema
34
+ # when generating segments for custom (not base) ex VAZ2.4 the field generator will have to look in both schemas
35
+ # to resolve types and coded tables value.
36
+ # we will assign the other schema parser as a helper parser
37
+
38
+ pp.each{|profileName, parser|
39
+ helper_parser = pp.select{|key, value| key != profileName }
40
+ helper_parser = (helper_parser.empty?) ? nil: helper_parser.values.first
41
+ # a = TypeAwareFieldGenerator.new( parser, helper_parser)
42
+ #@fieldGenerators[profileName] = a
43
+ @fieldGenerators[profileName] = TypeAwareFieldGenerator.new( parser, helper_parser)
44
+ }
45
+
46
+ end
47
+
48
+ # build hl7 message using template as guideline
49
+ # def generate(message, template, parsers, isGroup=false)
50
+ def generate(message, useExVal)
51
+ # read MWB xml file into collection of metadata for each segment
52
+ metadata = build_metadata(useExVal)
53
+
54
+ # segment names
55
+ segments = metadata.keys
56
+
57
+ # add each segment to message using template metadata
58
+ segments.each{|segName|
59
+ meta = metadata[segName]
60
+ processed = process_partials(meta)
61
+ message << generate_segment(segName, processed)
62
+ }
63
+
64
+ return message
65
+ end
66
+
67
+ # generate a segment using Ensemble schema
68
+ def generate_segment(segmentName, attributes, idx=nil)
69
+ # elements = generate_segment_elements(segmentName, attributes)
70
+ attributes.unshift(get_name_without_base(segmentName))
71
+ # elements = generate_segment_elements(segmentName, attributes)
72
+ # overrite ids for sequential repeating segments
73
+ # elements[@@SET_ID_PIECE] = handle_set_id(segmentName, attributes, idx) || elements[@@SET_ID_PIECE]
74
+
75
+ #generate segment using elements
76
+ if(segmentName == 'MSH')
77
+ attributes.slice!(1)
78
+ # one = two.first + two.last
79
+ # attributes.insert(1,one)
80
+ end
81
+ return HL7::Message::Segment::Default.new(attributes)
82
+ end
83
+
84
+ # working with template hash brake field metadata to components and then to subcomponents
85
+ def process_partials(item)
86
+ partials = []
87
+
88
+ # process each sub type
89
+ if(item.kind_of? Array)
90
+
91
+ item.each{|subType| # at this level we have components or subcomponents
92
+ coll = subType[SUB] || subType # if subcomponents found process again
93
+ unit = process_partials(coll)
94
+ flag = (subType[SUB]) ? @@SUB : @@HAT
95
+ partials[subType[:Pos].to_i] = unit.join(flag)
96
+ }
97
+
98
+ else
99
+ # check for components first and then subcomponents
100
+ coll = item[COMP] || item[SUB]
101
+ if(coll)
102
+ partials << process_partials(coll)
103
+ else
104
+ partials << build_partial_field_data(item)
105
+ end
106
+ end
107
+
108
+ return partials
109
+
110
+ end
111
+
112
+ # convert
113
+ def build_partial_field_data(item)
114
+ # use the Example Value 'ExValue' defined in the xml file instead
115
+ if(item[:ExValue]) then return item[:ExValue] end
116
+
117
+ # convert attributes from MWB into Ensemble and use the
118
+ attrs = {}
119
+ # strip leading zeros from table name, MWB format.
120
+ if (item[:Table]) then attrs[:codetable]= item[:Table].sub(/^0+/, '') end
121
+ if (item[:Length]) then attrs[:max_length]= item[:Length] end
122
+ if (item[:Name]) then attrs[:description] = item[:Name] end
123
+ if (item[:Datatype]) then attrs[:datatype] = item[:Datatype] end
124
+
125
+ # return genereated field
126
+ # use primary field generator, as templates only work for custom schema.
127
+ @fieldGenerators[PRIMARY].method(attrs[:datatype]).call(attrs, true)
128
+ end
129
+
130
+ # using MWB profiles build collection of message metadata, to use for building a message
131
+ def build_metadata(useExVal)
132
+ meta = {}
133
+ segments = []
134
+
135
+ # list of segments
136
+ # In the event of Subgroups of Segments defined in the template, find them and allow
137
+ # groups only once for simplicity.
138
+ if( @xml.HL7v2xConformanceProfile.HL7v2xStaticDef.locate('SegGroup')) then
139
+ nodes = @xml.HL7v2xConformanceProfile.HL7v2xStaticDef.nodes
140
+
141
+ nodes.each{|node|
142
+ if (node.value == "Segment") then
143
+ segments << node
144
+ elsif (node.value == "SegGroup")then
145
+ segments << node.locate('Segment')
146
+ end
147
+ }
148
+ segments.flatten!
149
+ else
150
+ segments = @xml.HL7v2xConformanceProfile.HL7v2xStaticDef.locate('Segment')
151
+ end
152
+
153
+ segments.each{|seg|
154
+
155
+ fields = []
156
+ seg.locate('Field').each_with_index { |f,idx |
157
+ # if (@@USAGES.include?(f.Usage))
158
+ if use?(f.Usage)
159
+ f.attributes.merge!(:Pos => idx)
160
+ fields << get_metadata(f,useExVal)
161
+ end
162
+ }
163
+
164
+ meta[seg.attributes[:Name]] = fields
165
+ }
166
+
167
+ return meta
168
+ end
169
+
170
+ # parse template xml file into collection
171
+ def get_metadata(partial, useExVal)
172
+
173
+ element = partial.locate(COMPONENT)
174
+
175
+ # first look for DataValues example if it's there use it and stop looking any farther.
176
+ if(useExVal && (element.empty?) && !partial.locate(DATAVALUES).empty?)
177
+ exVal = partial.locate(DATAVALUES).first.attributes
178
+ partial.attributes.merge!(exVal)
179
+ return partial.attributes
180
+ end
181
+
182
+ # continue looking for subcomponents
183
+ element = (element.empty?) ? partial.locate(SUBCOMPONENT) : element
184
+ if(!element.empty?)
185
+ sub = []
186
+
187
+ element.each_with_index { |el, idx|
188
+
189
+ if (use?(el[:Usage])) # required or optional
190
+ el.attributes.merge!(:Pos => idx)
191
+ sub << get_metadata(el, useExVal)
192
+ end
193
+
194
+ }
195
+
196
+ if(!sub.empty?)
197
+ subElementName = (element.first.value.downcase).intern # subcomponent or component
198
+ partial.attributes[subElementName] = sub
199
+ end
200
+
201
+ end
202
+
203
+ return partial.attributes
204
+
205
+ end
206
+
207
+ def use?(usage)
208
+ # check for required field/compnent/subcomponent: R
209
+ # if not toss a coin for optional (required or empty): RE (required or empty)
210
+ return (USAGES_REQ.include?(usage)) ? true : (USAGES_OPT.include?(usage) && [true,false].sample)
211
+ end
212
+
213
+ end