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.
- checksums.yaml +7 -0
- data/bin/ez7gen +6 -0
- data/lib/ez7gen.rb +23 -0
- data/lib/ez7gen/config/schema/2.4/2.4.HL7.xml +13904 -0
- data/lib/ez7gen/config/schema/2.4/VAZ2.4.HL7.xml +3085 -0
- data/lib/ez7gen/config/schema/2.4/added/coded-tables.xml +730 -0
- data/lib/ez7gen/config/schema/2.4/rules/2.4.HL7.yml +4 -0
- data/lib/ez7gen/config/schema/2.4/rules/VAZ2.4.HL7.yml +6 -0
- data/lib/ez7gen/config/schema/2.5/2.5.HL7.xml +10008 -0
- data/lib/ez7gen/config/schema/2.5/VAZ2.5.HL7.xml +7 -0
- data/lib/ez7gen/config/schema/2.5/added/coded-tables.xml +549 -0
- data/lib/ez7gen/config/schema/readme.txt +0 -0
- data/lib/ez7gen/config/templates/2.4/eiv table update-mfn_m01 20151201.xml +416 -0
- data/lib/ez7gen/config/templates/2.4/eiv table update-mfn_y01.xml +416 -0
- data/lib/ez7gen/config/templates/2.4/eiv-ec-MFN_X01_reg request 20160126.xml +659 -0
- data/lib/ez7gen/config/templates/2.4/examples/ADT_A60.txt +69 -0
- data/lib/ez7gen/config/templates/2.4/examples/eiv table update-mfn_m01 20151201.txt +21 -0
- data/lib/ez7gen/config/templates/2.4/examples/mhvsm_dss_units-query_qbp_q13-qbp_q13.txt +26 -0
- data/lib/ez7gen/config/templates/2.4/examples/mhvsm_ecs_procedures_query_qbp_q13-qbp_q13.txt +26 -0
- data/lib/ez7gen/config/templates/2.4/examples/mhvsm_patient eligibility_response-rsp_k11-080714.txt +44 -0
- data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_diagnosis_query_qbp_q11-qbp_q11.txt +21 -0
- data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_diagnosis_response_rsp_k11-rsp_k11.txt +42 -0
- data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_dss_units_response_rtb_k13-rtb_k13.txt +49 -0
- data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_ecs_filer_request_dft_p03-dft_p03-080714.txt +31 -0
- data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_ecs_filer_response_ack_p03-ack_p03.txt +21 -0
- data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_esc_procedures_response_rtb_k13-rtb_k13.txt +40 -0
- data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_patient_eclass_query_qbp_q11-qbp_q11.txt +21 -0
- data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_patient_problems_query_qbp_q11-qbp_q11.txt +21 -0
- data/lib/ez7gen/config/templates/2.4/examples/mhvsm_standardhl7lib_patinet_problems_response_rsp_k11-rsp_k11.txt +33 -0
- data/lib/ez7gen/config/templates/2.4/examples/orur01rvbecv2.txt +31 -0
- data/lib/ez7gen/config/templates/2.4/examples/sqwm vitals-oru_ro1.txt +52 -0
- data/lib/ez7gen/config/templates/2.4/examples/vista sqwm-adt_a60.txt +40 -0
- data/lib/ez7gen/config/templates/2.4/mhvsm_dss_units_query_qbp_q13-qbp_q13.xml +312 -0
- data/lib/ez7gen/config/templates/2.4/mhvsm_ecs_procedures_query_qbp_q13-qbp_q13.xml +314 -0
- data/lib/ez7gen/config/templates/2.4/mhvsm_patient eligibility_response-rsp_k11-080714.xml +640 -0
- data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_diagnosis_query_qbp_q11-qbp_q11.xml +284 -0
- data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_diagnosis_response_rsp_k11-rsp_k11-rsp_k11.xml +563 -0
- data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_dss_units_response_rtb_k13-rtb_k13-rtb_k13.xml +365 -0
- data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_ecs_filer_request_dft_p03-dft_p03-080714.xml +2172 -0
- data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_ecs_filer_response_ack_p03-ack_p03.xml +269 -0
- data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_ecs_procedures_response_rtb_k13-rtb_k13-rtb_k13.xml +354 -0
- data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_patient_eclass_query_qbp_q11-qbp_q11.xml +284 -0
- data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_patient_problems_query_qbp_q11-qbp_q11.xml +282 -0
- data/lib/ez7gen/config/templates/2.4/mhvsm_standardhl7lib_patient_problems_response_rsp_k11-rsp_k11-rsp_k11.xml +565 -0
- data/lib/ez7gen/config/templates/2.4/orur01rvbecv2.xml +1529 -0
- data/lib/ez7gen/config/templates/2.4/sqwm vitals-oru_r01.xml +2975 -0
- data/lib/ez7gen/config/templates/2.4/vista sqwm-adt_a60.xml +1360 -0
- data/lib/ez7gen/message_factory.rb +142 -0
- data/lib/ez7gen/msg_error_handler.rb +33 -0
- data/lib/ez7gen/profile_parser.rb +321 -0
- data/lib/ez7gen/resources/properties-with-comments.yml +51 -0
- data/lib/ez7gen/resources/properties.yml +325 -0
- data/lib/ez7gen/service/2.4/dynamic_field_generator.rb +45 -0
- data/lib/ez7gen/service/2.4/field_generator.rb +1586 -0
- data/lib/ez7gen/service/2.5/field_generator.rb +75 -0
- data/lib/ez7gen/service/base_field_generator.rb +451 -0
- data/lib/ez7gen/service/segment_generator.rb +218 -0
- data/lib/ez7gen/service/segment_picker.rb +147 -0
- data/lib/ez7gen/service/template_generator.rb +213 -0
- data/lib/ez7gen/service/type_aware_field_generator.rb +1583 -0
- data/lib/ez7gen/service/utils.rb +75 -0
- data/lib/ez7gen/structure_parser.rb +331 -0
- data/lib/ez7gen/version.rb +38 -0
- data/test/Additional Tables with values_v1.1.txt +1653 -0
- data/test/added_shema_test.rb +143 -0
- data/test/app-tmp.rb +225 -0
- data/test/at.txt +1 -0
- data/test/backburner.zip +0 -0
- data/test/codes.txt +262 -0
- data/test/codes1.txt +1240 -0
- data/test/data_types_exploration_test.rb +213 -0
- data/test/dynamic_field_generated_test.rb +292 -0
- data/test/message_factory_24_custom_test.rb +648 -0
- data/test/message_factory_25_test.rb +50 -0
- data/test/message_factory_adm_test.rb +558 -0
- data/test/message_factory_gen_test.rb +63 -0
- data/test/message_factory_lab_test.rb +107 -0
- data/test/message_factory_pharm_test.rb +121 -0
- data/test/message_factory_template_24_test.rb +730 -0
- data/test/message_factory_test.rb +220 -0
- data/test/msg_error_handler_test.rb +59 -0
- data/test/profile_parser_test.rb +542 -0
- data/test/quick_run.rb +880 -0
- data/test/segment_generator_test.rb +656 -0
- data/test/segment_picker_test.rb +279 -0
- data/test/structrure_parser_test.rb +355 -0
- data/test/template_generator_test.rb +164 -0
- data/test/type_aware_field_generator_test.rb +582 -0
- data/test/utils_test.rb +97 -0
- 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
|