ez7gen 1.0.1

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