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