rtp-connect 1.6 → 1.11

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.
@@ -1,63 +1,86 @@
1
- module RTP
2
-
3
- class << self
4
-
5
- #--
6
- # Module methods:
7
- #++
8
-
9
- # Gives an array of MLC leaf position boundaries for a given type of MLC,
10
- # specified by its number of leaves at one side.
11
- #
12
- # @param [Fixnum] nr_leaves the number of leaves (in one leaf bank)
13
- # @return [Array<Fixnum>] the leaf boundary positions
14
- # @raise [ArgumentError] if an unsupported MLC (nr of leaves) is given
15
- #
16
- def leaf_boundaries(nr_leaves)
17
- case nr_leaves
18
- when 29
19
- [-200, -135, -125, -115, -105, -95, -85, -75, -65, -55, -45, -35, -25,
20
- -15, -5, 5, 15, 25, 35, 45, 55, 65, 75, 85, 95, 105, 115, 125, 135, 200
21
- ]
22
- when 40
23
- Array.new(nr_leaves+1) {|i| (i * 400 / nr_leaves.to_f - 200).to_i}
24
- when 41
25
- [-200, -195, -185, -175, -165, -155, -145, -135, -125, -115,
26
- -105, -95, -85, -75, -65, -55, -45, -35, -25, -15, -5, 5, 15, 25, 35, 45,
27
- 55, 65, 75, 85, 95, 105, 115, 125, 135, 145, 155, 165, 175, 185, 195, 200
28
- ]
29
- when 60
30
- [-200, -190, -180, -170, -160, -150, -140, -130, -120, -110,
31
- -100, -95, -90, -85, -80, -75, -70, -65, -60, -55, -50, -45, -40, -35, -30,
32
- -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65,
33
- 70, 75, 80, 85, 90, 95, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200
34
- ]
35
- when 80
36
- Array.new(nr_leaves+1) {|i| (i * 400 / nr_leaves.to_f - 200).to_i}
37
- else
38
- raise ArgumentError, "Unsupported number of leaves: #{nr_leaves}"
39
- end
40
- end
41
-
42
- # Computes the CRC checksum of the given line and verifies that
43
- # this value corresponds with the checksum given at the end of the line.
44
- #
45
- # @param [String] line a single line string from an RTPConnect ascii file
46
- # @return [Boolean] true
47
- # @raise [ArgumentError] if an invalid line/record is given or the string contains an invalid checksum
48
- #
49
- def verify(line)
50
- last_comma_pos = line.rindex(',')
51
- raise ArgumentError, "Invalid line encountered; No comma present in the string: #{line}" unless last_comma_pos
52
- string_to_check = line[0..last_comma_pos]
53
- string_remaining = line[(last_comma_pos+1)..-1]
54
- raise ArgumentError, "Invalid line encountered; Valid checksum missing at end of string: #{string_remaining}" unless string_remaining.length >= 3
55
- checksum_extracted = string_remaining.value.to_i
56
- checksum_computed = string_to_check.checksum
57
- raise ArgumentError, "Invalid line encountered: Specified checskum #{checksum_extracted} deviates from the computed checksum #{checksum_computed}." if checksum_extracted != checksum_computed
58
- return true
59
- end
60
-
61
- end
62
-
1
+ module RTP
2
+
3
+ class << self
4
+
5
+ #--
6
+ # Module methods:
7
+ #++
8
+
9
+ # Gives an array of MLC leaf position boundaries for a given type of MLC,
10
+ # specified by its number of leaves at one side.
11
+ #
12
+ # @param [Fixnum] nr_leaves the number of leaves (in one leaf bank)
13
+ # @return [Array<Fixnum>] the leaf boundary positions
14
+ # @raise [ArgumentError] if an unsupported MLC (nr of leaves) is given
15
+ #
16
+ def leaf_boundaries(nr_leaves)
17
+ case nr_leaves
18
+ when 29
19
+ leaf_boundaries_odd(29)
20
+ when 40
21
+ leaf_boundaries_even(40)
22
+ when 41
23
+ leaf_boundaries_odd(41)
24
+ when 60
25
+ leaf_boundaries_varian_60
26
+ when 80
27
+ leaf_boundaries_even(80)
28
+ else
29
+ raise ArgumentError, "Unsupported number of leaves: #{nr_leaves}"
30
+ end
31
+ end
32
+
33
+ # Gives an array of MLC leaf position boundaries for ordinary even numbered
34
+ # multi leaf collimators.
35
+ #
36
+ # @param [Fixnum] nr_leaves the number of leaves (in one leaf bank)
37
+ # @return [Array<Fixnum>] the leaf boundary positions
38
+ #
39
+ def leaf_boundaries_even(nr_leaves)
40
+ Array.new(nr_leaves+1) {|i| (i * 400 / nr_leaves.to_f - 200).to_i}
41
+ end
42
+
43
+ # Gives an array of MLC leaf position boundaries for ordinary odd numbered
44
+ # multi leaf collimators.
45
+ #
46
+ # @param [Fixnum] nr_leaves the number of leaves (in one leaf bank)
47
+ # @return [Array<Fixnum>] the leaf boundary positions
48
+ #
49
+ def leaf_boundaries_odd(nr_leaves)
50
+ Array.new(nr_leaves-1) {|i| (10 * (i - (0.5 * nr_leaves - 1))).to_i}.unshift(-200).push(200)
51
+ end
52
+
53
+ # Gives an array of MLC leaf position boundaries for the Varian 120 leaf MLC (60 leaves on each bank).
54
+ #
55
+ # @return [Array<Fixnum>] the leaf boundary positions
56
+ #
57
+ def leaf_boundaries_varian_60
58
+ Array.new(10) {|i| (i * 10 - 200).to_i}
59
+ .concat(Array.new(41) {|i| (i * 5 - 100).to_i})
60
+ .concat(Array.new(10) {|i| (i * 10 + 110).to_i})
61
+ end
62
+
63
+ # Computes the CRC checksum of the given line and verifies that
64
+ # this value corresponds with the checksum given at the end of the line.
65
+ #
66
+ # @param [String] line a single line string from an RTPConnect ascii file
67
+ # @param [Hash] options the options to use for verifying the RTP record
68
+ # @option options [Boolean] :ignore_crc if true, the verification method will return true even if the checksum is invalid
69
+ # @return [Boolean] true
70
+ # @raise [ArgumentError] if an invalid line/record is given or the string contains an invalid checksum
71
+ #
72
+ def verify(line, options={})
73
+ last_comma_pos = line.rindex(',')
74
+ raise ArgumentError, "Invalid line encountered; No comma present in the string: #{line}" unless last_comma_pos
75
+ string_to_check = line[0..last_comma_pos]
76
+ string_remaining = line[(last_comma_pos+1)..-1]
77
+ raise ArgumentError, "Invalid line encountered; Valid checksum missing at end of string: #{string_remaining}" unless string_remaining.length >= 3
78
+ checksum_extracted = string_remaining.value.to_i
79
+ checksum_computed = string_to_check.checksum
80
+ raise ArgumentError, "Invalid line encountered: Specified checksum #{checksum_extracted} deviates from the computed checksum #{checksum_computed}." if checksum_extracted != checksum_computed && !options[:ignore_crc]
81
+ return true
82
+ end
83
+
84
+ end
85
+
63
86
  end
@@ -1,637 +1,646 @@
1
- # Copyright 2011-2013 Christoffer Lervag
2
- #
3
- # This program is free software: you can redistribute it and/or modify
4
- # it under the terms of the GNU General Public License as published by
5
- # the Free Software Foundation, either version 3 of the License, or
6
- # (at your option) any later version.
7
- #
8
- # This program is distributed in the hope that it will be useful,
9
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
- # GNU General Public License for more details.
12
- #
13
- # You should have received a copy of the GNU General Public License
14
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
- #
16
- module RTP
17
-
18
- # The Plan class is the highest level Record in the RTPConnect records hierarchy,
19
- # and the one the user will interact with to read, modify and write files.
20
- #
21
- # @note Relations:
22
- # * Parent: nil
23
- # * Children: Prescription, DoseTracking
24
- #
25
- class Plan < Record
26
- include Logging
27
-
28
- # The Record which this instance belongs to (nil by definition).
29
- attr_reader :parent
30
- # An array of Prescription records (if any) that belongs to this Plan.
31
- attr_reader :prescriptions
32
- # An array of DoseTracking records (if any) that belongs to this Plan.
33
- attr_reader :dose_trackings
34
- attr_reader :patient_id
35
- attr_reader :patient_last_name
36
- attr_reader :patient_first_name
37
- attr_reader :patient_middle_initial
38
- attr_reader :plan_id
39
- attr_reader :plan_date
40
- attr_reader :plan_time
41
- attr_reader :course_id
42
- attr_reader :diagnosis
43
- attr_reader :md_last_name
44
- attr_reader :md_first_name
45
- attr_reader :md_middle_initial
46
- attr_reader :md_approve_last_name
47
- attr_reader :md_approve_first_name
48
- attr_reader :md_approve_middle_initial
49
- attr_reader :phy_approve_last_name
50
- attr_reader :phy_approve_first_name
51
- attr_reader :phy_approve_middle_initial
52
- attr_reader :author_last_name
53
- attr_reader :author_first_name
54
- attr_reader :author_middle_initial
55
- attr_reader :rtp_mfg
56
- attr_reader :rtp_model
57
- attr_reader :rtp_version
58
- attr_reader :rtp_if_protocol
59
- attr_reader :rtp_if_version
60
-
61
- # Creates a new Plan by loading a plan definition string (i.e. a single line).
62
- #
63
- # @note This method does not perform crc verification on the given string.
64
- # If such verification is desired, use methods ::parse or ::read instead.
65
- # @param [#to_s] string the plan definition record string line
66
- # @return [Plan] the created Plan instance
67
- # @raise [ArgumentError] if given a string containing an invalid number of elements
68
- #
69
- def self.load(string)
70
- # Get the quote-less values:
71
- values = string.to_s.values
72
- low_limit = 10
73
- high_limit = 28
74
- raise ArgumentError, "Invalid argument 'string': Expected at least #{low_limit} elements, got #{values.length}." if values.length < low_limit
75
- RTP.logger.warn "The number of elements (#{values.length}) for this Plan record exceeds the known number of data items for this record (#{high_limit}). This may indicate an invalid record or that the RTP format has recently been expanded with new items." if values.length > high_limit
76
- rtp = self.new
77
- # Assign the values to attributes:
78
- rtp.keyword = values[0]
79
- rtp.patient_id = values[1]
80
- rtp.patient_last_name = values[2]
81
- rtp.patient_first_name = values[3]
82
- rtp.patient_middle_initial = values[4]
83
- rtp.plan_id = values[5]
84
- rtp.plan_date = values[6]
85
- rtp.plan_time = values[7]
86
- rtp.course_id = values[8]
87
- rtp.diagnosis = values[9]
88
- rtp.md_last_name = values[10]
89
- rtp.md_first_name = values[11]
90
- rtp.md_middle_initial = values[12]
91
- rtp.md_approve_last_name = values[13]
92
- rtp.md_approve_first_name = values[14]
93
- rtp.md_approve_middle_initial = values[15]
94
- rtp.phy_approve_last_name = values[16]
95
- rtp.phy_approve_first_name = values[17]
96
- rtp.phy_approve_middle_initial = values[18]
97
- rtp.author_last_name = values[19]
98
- rtp.author_first_name = values[20]
99
- rtp.author_middle_initial = values[21]
100
- rtp.rtp_mfg = values[22]
101
- rtp.rtp_model = values[23]
102
- rtp.rtp_version = values[24]
103
- rtp.rtp_if_protocol = values[25]
104
- rtp.rtp_if_version = values[26]
105
- rtp.crc = values[-1]
106
- return rtp
107
- end
108
-
109
- # Creates a Plan instance by parsing an RTPConnect string.
110
- #
111
- # @param [#to_s] string an RTPConnect ascii string (with single or multiple lines/records)
112
- # @return [Plan] the created Plan instance
113
- # @raise [ArgumentError] if given an invalid string record
114
- #
115
- def self.parse(string)
116
- lines = string.to_s.split("\r\n")
117
- # Create the Plan object:
118
- line = lines.first
119
- RTP::verify(line)
120
- rtp = self.load(line)
121
- lines[1..-1].each do |line|
122
- # Validate, determine type, and process the line accordingly to
123
- # build the hierarchy of records:
124
- RTP::verify(line)
125
- values = line.values
126
- keyword = values.first
127
- method = RTP::PARSE_METHOD[keyword]
128
- raise ArgumentError, "Unknown keyword #{keyword} extracted from string." unless method
129
- rtp.send(method, line)
130
- end
131
- return rtp
132
- end
133
-
134
- # Creates an Plan instance by reading and parsing an RTPConnect file.
135
- #
136
- # @param [String] file a string which specifies the path of the RTPConnect file to be loaded
137
- # @return [Plan] the created Plan instance
138
- # @raise [ArgumentError] if given an invalid file or the file given contains an invalid record
139
- #
140
- def self.read(file)
141
- raise ArgumentError, "Invalid argument 'file'. Expected String, got #{file.class}." unless file.is_a?(String)
142
- # Read the file content:
143
- str = nil
144
- unless File.exist?(file)
145
- logger.error("Invalid (non-existing) file: #{file}")
146
- else
147
- unless File.readable?(file)
148
- logger.error("File exists but I don't have permission to read it: #{file}")
149
- else
150
- if File.directory?(file)
151
- logger.error("Expected a file, got a directory: #{file}")
152
- else
153
- if File.size(file) < 10
154
- logger.error("This file is too small to contain valid RTP information: #{file}.")
155
- else
156
- str = File.open(file, 'rb:ISO8859-1') { |f| f.read }
157
- end
158
- end
159
- end
160
- end
161
- # Parse the file contents and create the RTP::Connect object:
162
- if str
163
- rtp = self.parse(str)
164
- else
165
- raise "An RTP::Plan object could not be created from the specified file. Check the log for more details."
166
- end
167
- return rtp
168
- end
169
-
170
- # Creates a new Plan.
171
- #
172
- def initialize
173
- @current_parent = self
174
- # Child records:
175
- @prescriptions = Array.new
176
- @dose_trackings = Array.new
177
- # No parent (by definition) for the Plan record:
178
- @parent = nil
179
- @keyword = 'PLAN_DEF'
180
- end
181
-
182
- # Checks for equality.
183
- #
184
- # Other and self are considered equivalent if they are
185
- # of compatible types and their attributes are equivalent.
186
- #
187
- # @param other an object to be compared with self.
188
- # @return [Boolean] true if self and other are considered equivalent
189
- #
190
- def ==(other)
191
- if other.respond_to?(:to_plan)
192
- other.send(:state) == state
193
- end
194
- end
195
-
196
- alias_method :eql?, :==
197
-
198
- # Adds a dose tracking record to this instance.
199
- #
200
- # @param [DoseTracking] child a DoseTracking instance which is to be associated with self
201
- #
202
- def add_dose_tracking(child)
203
- @dose_trackings << child.to_dose_tracking
204
- end
205
-
206
- # Adds a prescription site record to this instance.
207
- #
208
- # @param [Prescription] child a Prescription instance which is to be associated with self
209
- #
210
- def add_prescription(child)
211
- @prescriptions << child.to_prescription
212
- end
213
-
214
- # Collects the child records of this instance in a properly sorted array.
215
- #
216
- # @return [Array<Prescription, DoseTracking>] a sorted array of self's child records
217
- #
218
- def children
219
- return [@prescriptions, @dose_trackings].flatten.compact
220
- end
221
-
222
- # Computes a hash code for this object.
223
- #
224
- # @note Two objects with the same attributes will have the same hash code.
225
- #
226
- # @return [Fixnum] the object's hash code
227
- #
228
- def hash
229
- state.hash
230
- end
231
-
232
- # Collects the values (attributes) of this instance.
233
- #
234
- # @note The CRC is not considered part of the actual values and is excluded.
235
- # @return [Array<String>] an array of attributes (in the same order as they appear in the RTP string)
236
- #
237
- def values
238
- return [
239
- @keyword,
240
- @patient_id,
241
- @patient_last_name,
242
- @patient_first_name,
243
- @patient_middle_initial,
244
- @plan_id,
245
- @plan_date,
246
- @plan_time,
247
- @course_id,
248
- @diagnosis,
249
- @md_last_name,
250
- @md_first_name,
251
- @md_middle_initial,
252
- @md_approve_last_name,
253
- @md_approve_first_name,
254
- @md_approve_middle_initial,
255
- @phy_approve_last_name,
256
- @phy_approve_first_name,
257
- @phy_approve_middle_initial,
258
- @author_last_name,
259
- @author_first_name,
260
- @author_middle_initial,
261
- @rtp_mfg,
262
- @rtp_model,
263
- @rtp_version,
264
- @rtp_if_protocol,
265
- @rtp_if_version
266
- ]
267
- end
268
-
269
- # Returns self.
270
- #
271
- # @return [Plan] self
272
- #
273
- def to_plan
274
- self
275
- end
276
-
277
- # Returns self.
278
- #
279
- # @return [Plan] self
280
- #
281
- def to_rtp
282
- self
283
- end
284
-
285
- # Encodes the Plan object + any hiearchy of child objects,
286
- # to a properly formatted RTPConnect ascii string.
287
- #
288
- # @return [String] an RTP string with a single or multiple lines/records
289
- #
290
- def to_s
291
- str = encode #.force_encoding('utf-8')
292
- children.each do |child|
293
- str += child.to_s #.force_encoding('utf-8')
294
- end
295
- return str
296
- end
297
-
298
- alias :to_str :to_s
299
-
300
- # Writes the Plan object, along with its hiearchy of child objects,
301
- # to a properly formatted RTPConnect ascii file.
302
- #
303
- # @param [String] file a path/file string
304
- #
305
- def write(file)
306
- f = open_file(file)
307
- f.write(to_s)
308
- f.close
309
- end
310
-
311
- # Sets the keyword attribute.
312
- #
313
- # @note Since only a specific string is accepted, this is more of an argument check than a traditional setter method
314
- # @param [#to_s] value the new attribute value
315
- # @raise [ArgumentError] if given an unexpected keyword
316
- #
317
- def keyword=(value)
318
- value = value.to_s.upcase
319
- raise ArgumentError, "Invalid keyword. Expected 'PLAN_DEF', got #{value}." unless value == "PLAN_DEF"
320
- @keyword = value
321
- end
322
-
323
- # Sets the patient_id attribute.
324
- #
325
- # @param [nil, #to_s] value the new attribute value
326
- #
327
- def patient_id=(value)
328
- @patient_id = value && value.to_s
329
- end
330
-
331
- # Sets the patient_last_name attribute.
332
- #
333
- def patient_last_name=(value)
334
- @patient_last_name = value && value.to_s
335
- end
336
-
337
- # Sets the patient_first_name attribute.
338
- #
339
- # @param [nil, #to_s] value the new attribute value
340
- #
341
- def patient_first_name=(value)
342
- @patient_first_name = value && value.to_s
343
- end
344
-
345
- # Sets the patient_middle_initial attribute.
346
- #
347
- # @param [nil, #to_s] value the new attribute value
348
- #
349
- def patient_middle_initial=(value)
350
- @patient_middle_initial = value && value.to_s
351
- end
352
-
353
- # Sets the plan_id attribute.
354
- #
355
- # @param [nil, #to_s] value the new attribute value
356
- #
357
- def plan_id=(value)
358
- @plan_id = value && value.to_s
359
- end
360
-
361
- # Sets the plan_date attribute.
362
- #
363
- # @param [nil, #to_s] value the new attribute value
364
- #
365
- def plan_date=(value)
366
- @plan_date = value && value.to_s
367
- end
368
-
369
- # Sets the plan_time attribute.
370
- #
371
- # @param [nil, #to_s] value the new attribute value
372
- #
373
- def plan_time=(value)
374
- @plan_time = value && value.to_s
375
- end
376
-
377
- # Sets the course_id attribute.
378
- #
379
- # @param [nil, #to_s] value the new attribute value
380
- #
381
- def course_id=(value)
382
- @course_id = value && value.to_s
383
- end
384
-
385
- # Sets the diagnosis attribute.
386
- #
387
- # @param [nil, #to_s] value the new attribute value
388
- #
389
- def diagnosis=(value)
390
- @diagnosis = value && value.to_s
391
- end
392
-
393
- # Sets the md_last_name attribute.
394
- #
395
- # @param [nil, #to_s] value the new attribute value
396
- #
397
- def md_last_name=(value)
398
- @md_last_name = value && value.to_s
399
- end
400
-
401
- # Sets the md_first_name attribute.
402
- #
403
- # @param [nil, #to_s] value the new attribute value
404
- #
405
- def md_first_name=(value)
406
- @md_first_name = value && value.to_s
407
- end
408
-
409
- # Sets the md_middle_initial attribute.
410
- #
411
- # @param [nil, #to_s] value the new attribute value
412
- #
413
- def md_middle_initial=(value)
414
- @md_middle_initial = value && value.to_s
415
- end
416
-
417
- # Sets the md_approve_last_name attribute.
418
- #
419
- # @param [nil, #to_s] value the new attribute value
420
- #
421
- def md_approve_last_name=(value)
422
- @md_approve_last_name = value && value.to_s
423
- end
424
-
425
- # Sets the md_approve_first_name attribute.
426
- #
427
- # @param [nil, #to_s] value the new attribute value
428
- #
429
- def md_approve_first_name=(value)
430
- @md_approve_first_name = value && value.to_s
431
- end
432
-
433
- # Sets the md_approve_middle_initial attribute.
434
- #
435
- # @param [nil, #to_s] value the new attribute value
436
- #
437
- def md_approve_middle_initial=(value)
438
- @md_approve_middle_initial = value && value.to_s
439
- end
440
-
441
- # Sets the phy_approve_last_name attribute.
442
- #
443
- # @param [nil, #to_s] value the new attribute value
444
- #
445
- def phy_approve_last_name=(value)
446
- @phy_approve_last_name = value && value.to_s
447
- end
448
-
449
- # Sets the phy_approve_first_name attribute.
450
- #
451
- # @param [nil, #to_s] value the new attribute value
452
- #
453
- def phy_approve_first_name=(value)
454
- @phy_approve_first_name = value && value.to_s
455
- end
456
-
457
- # Sets the phy_approve_middle_initial attribute.
458
- #
459
- # @param [nil, #to_s] value the new attribute value
460
- #
461
- def phy_approve_middle_initial=(value)
462
- @phy_approve_middle_initial = value && value.to_s
463
- end
464
-
465
- # Sets the author_last_name attribute.
466
- #
467
- # @param [nil, #to_s] value the new attribute value
468
- #
469
- def author_last_name=(value)
470
- @author_last_name = value && value.to_s
471
- end
472
-
473
- # Sets the author_first_name attribute.
474
- #
475
- # @param [nil, #to_s] value the new attribute value
476
- #
477
- def author_first_name=(value)
478
- @author_first_name = value && value.to_s
479
- end
480
-
481
- # Sets the author_middle_initial attribute.
482
- #
483
- # @param [nil, #to_s] value the new attribute value
484
- #
485
- def author_middle_initial=(value)
486
- @author_middle_initial = value && value.to_s
487
- end
488
-
489
- # Sets the rtp_mfg attribute.
490
- #
491
- # @param [nil, #to_s] value the new attribute value
492
- #
493
- def rtp_mfg=(value)
494
- @rtp_mfg = value && value.to_s
495
- end
496
-
497
- # Sets the rtp_model attribute.
498
- #
499
- # @param [nil, #to_s] value the new attribute value
500
- #
501
- def rtp_model=(value)
502
- @rtp_model = value && value.to_s
503
- end
504
-
505
- # Sets the rtp_version attribute.
506
- #
507
- # @param [nil, #to_s] value the new attribute value
508
- #
509
- def rtp_version=(value)
510
- @rtp_version = value && value.to_s
511
- end
512
-
513
- # Sets the rtp_if_protocol attribute.
514
- #
515
- # @param [nil, #to_s] value the new attribute value
516
- #
517
- def rtp_if_protocol=(value)
518
- @rtp_if_protocol = value && value.to_s
519
- end
520
-
521
- # Sets the rtp_if_version attribute.
522
- #
523
- # @param [nil, #to_s] value the new attribute value
524
- #
525
- def rtp_if_version=(value)
526
- @rtp_if_version = value && value.to_s
527
- end
528
-
529
-
530
- private
531
-
532
-
533
- # Creates a control point record from the given string.
534
- #
535
- # @param [String] string a string line containing a control point definition
536
- #
537
- def control_point(string)
538
- cp = ControlPoint.load(string, @current_parent)
539
- @current_parent = cp
540
- end
541
-
542
- # Creates a dose tracking record from the given string.
543
- #
544
- # @param [String] string a string line containing a dose tracking definition
545
- #
546
- def dose_tracking(string)
547
- dt = DoseTracking.load(string, @current_parent)
548
- @current_parent = dt
549
- end
550
-
551
- # Creates an extended treatment field record from the given string.
552
- #
553
- # @param [String] string a string line containing an extended treatment field definition
554
- #
555
- def extended_treatment_field(string)
556
- ef = ExtendedField.load(string, @current_parent)
557
- @current_parent = ef
558
- end
559
-
560
- # Tests if the path/file is writable, creates any folders if necessary, and opens the file for writing.
561
- #
562
- # @param [String] file a path/file string
563
- # @raise if the given file cannot be created
564
- #
565
- def open_file(file)
566
- # Check if file already exists:
567
- if File.exist?(file)
568
- # Is (the existing file) writable?
569
- unless File.writable?(file)
570
- raise "The program does not have permission or resources to create this file: #{file}"
571
- end
572
- else
573
- # File does not exist.
574
- # Check if this file's path contains a folder that does not exist, and therefore needs to be created:
575
- folders = file.split(File::SEPARATOR)
576
- if folders.length > 1
577
- # Remove last element (which should be the file string):
578
- folders.pop
579
- path = folders.join(File::SEPARATOR)
580
- # Check if this path exists:
581
- unless File.directory?(path)
582
- # We need to create (parts of) this path:
583
- require 'fileutils'
584
- FileUtils.mkdir_p(path)
585
- end
586
- end
587
- end
588
- # It has been verified that the file can be created:
589
- return File.new(file, 'wb:ISO8859-1')
590
- end
591
-
592
- # Creates a prescription site record from the given string.
593
- #
594
- # @param [String] string a string line containing a prescription site definition
595
- #
596
- def prescription_site(string)
597
- p = Prescription.load(string, @current_parent)
598
- @current_parent = p
599
- end
600
-
601
- # Creates a site setup record from the given string.
602
- #
603
- # @param [String] string a string line containing a site setup definition
604
- #
605
- def site_setup(string)
606
- s = SiteSetup.load(string, @current_parent)
607
- @current_parent = s
608
- end
609
-
610
- # Collects the attributes of this instance.
611
- #
612
- # @note The CRC is not considered part of the attributes of interest and is excluded
613
- # @return [Array<String>] an array of attributes
614
- #
615
- alias_method :state, :values
616
-
617
- # Creates a treatment field record from the given string.
618
- #
619
- # @param [String] string a string line containing a treatment field definition
620
- #
621
- def treatment_field(string)
622
- f = Field.load(string, @current_parent)
623
- @current_parent = f
624
- end
625
-
626
- # Creates a simulation field record from the given string.
627
- #
628
- # @param [String] string a string line containing a simulation field definition
629
- #
630
- def simulation_field(string)
631
- sf = SimulationField.load(string, @current_parent)
632
- @current_parent = sf
633
- end
634
-
635
- end
636
-
1
+ # Copyright 2011-2020 Christoffer Lervag
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+ #
16
+ module RTP
17
+
18
+ # The Plan class is the highest level Record in the RTPConnect records hierarchy,
19
+ # and the one the user will interact with to read, modify and write files.
20
+ #
21
+ # @note Relations:
22
+ # * Parent: nil
23
+ # * Children: Prescription, DoseTracking
24
+ #
25
+ class Plan < Record
26
+ include Logging
27
+
28
+ # The Record which this instance belongs to (nil by definition).
29
+ attr_reader :parent
30
+ # An array of Prescription records (if any) that belongs to this Plan.
31
+ attr_reader :prescriptions
32
+ # The ExtendedPlan record (if any) that belongs to this Plan.
33
+ attr_reader :extended_plan
34
+ # An array of DoseTracking records (if any) that belongs to this Plan.
35
+ attr_reader :dose_trackings
36
+ attr_reader :patient_id
37
+ attr_reader :patient_last_name
38
+ attr_reader :patient_first_name
39
+ attr_reader :patient_middle_initial
40
+ attr_reader :plan_id
41
+ attr_reader :plan_date
42
+ attr_reader :plan_time
43
+ attr_reader :course_id
44
+ attr_reader :diagnosis
45
+ attr_reader :md_last_name
46
+ attr_reader :md_first_name
47
+ attr_reader :md_middle_initial
48
+ attr_reader :md_approve_last_name
49
+ attr_reader :md_approve_first_name
50
+ attr_reader :md_approve_middle_initial
51
+ attr_reader :phy_approve_last_name
52
+ attr_reader :phy_approve_first_name
53
+ attr_reader :phy_approve_middle_initial
54
+ attr_reader :author_last_name
55
+ attr_reader :author_first_name
56
+ attr_reader :author_middle_initial
57
+ attr_reader :rtp_mfg
58
+ attr_reader :rtp_model
59
+ attr_reader :rtp_version
60
+ attr_reader :rtp_if_protocol
61
+ attr_reader :rtp_if_version
62
+
63
+ # Creates a new Plan by loading a plan definition string (i.e. a single line).
64
+ #
65
+ # @note This method does not perform crc verification on the given string.
66
+ # If such verification is desired, use methods ::parse or ::read instead.
67
+ # @param [#to_s] string the plan definition record string line
68
+ # @param [Hash] options the options to use for loading the plan definition string
69
+ # @option options [Boolean] :repair if true, a record containing invalid CSV will be attempted fixed and loaded
70
+ # @return [Plan] the created Plan instance
71
+ # @raise [ArgumentError] if given a string containing an invalid number of elements
72
+ #
73
+ def self.load(string, options={})
74
+ rtp = self.new
75
+ rtp.load(string, options)
76
+ end
77
+
78
+ # Creates a Plan instance by parsing an RTPConnect string.
79
+ #
80
+ # @param [#to_s] string an RTPConnect ascii string (with single or multiple lines/records)
81
+ # @param [Hash] options the options to use for parsing the RTP string
82
+ # @option options [Boolean] :ignore_crc if true, the RTP records will be successfully loaded even if their checksums are invalid
83
+ # @option options [Boolean] :repair if true, any RTP records containing invalid CSV will be attempted fixed and loaded
84
+ # @option options [Boolean] :skip_unknown if true, unknown records will be skipped, and record instances will be built from the remaining recognized string records
85
+ # @return [Plan] the created Plan instance
86
+ # @raise [ArgumentError] if given an invalid string record
87
+ #
88
+ def self.parse(string, options={})
89
+ lines = string.to_s.split("\r\n")
90
+ # Create the Plan object:
91
+ line = lines.first
92
+ RTP.verify(line, options)
93
+ rtp = self.load(line, options)
94
+ lines[1..-1].each do |line|
95
+ # Validate, determine type, and process the line accordingly to
96
+ # build the hierarchy of records:
97
+ RTP.verify(line, options)
98
+ values = line.values(options[:repair])
99
+ keyword = values.first
100
+ method = RTP::PARSE_METHOD[keyword]
101
+ if method
102
+ rtp.send(method, line)
103
+ else
104
+ if options[:skip_unknown]
105
+ logger.warn("Skipped unknown record definition: #{keyword}")
106
+ else
107
+ raise ArgumentError, "Unknown keyword #{keyword} extracted from string."
108
+ end
109
+ end
110
+ end
111
+ return rtp
112
+ end
113
+
114
+ # Creates an Plan instance by reading and parsing an RTPConnect file.
115
+ #
116
+ # @param [String] file a string which specifies the path of the RTPConnect file to be loaded
117
+ # @param [Hash] options the options to use for reading the RTP file
118
+ # @option options [Boolean] :ignore_crc if true, the RTP records will be successfully loaded even if their checksums are invalid
119
+ # @option options [Boolean] :repair if true, any RTP records containing invalid CSV will be attempted fixed and loaded
120
+ # @option options [Boolean] :skip_unknown if true, unknown records will be skipped, and record instances will be built from the remaining recognized string records
121
+ # @return [Plan] the created Plan instance
122
+ # @raise [ArgumentError] if given an invalid file or the file given contains an invalid record
123
+ #
124
+ def self.read(file, options={})
125
+ raise ArgumentError, "Invalid argument 'file'. Expected String, got #{file.class}." unless file.is_a?(String)
126
+ # Read the file content:
127
+ str = nil
128
+ unless File.exist?(file)
129
+ logger.error("Invalid (non-existing) file: #{file}")
130
+ else
131
+ unless File.readable?(file)
132
+ logger.error("File exists but I don't have permission to read it: #{file}")
133
+ else
134
+ if File.directory?(file)
135
+ logger.error("Expected a file, got a directory: #{file}")
136
+ else
137
+ if File.size(file) < 10
138
+ logger.error("This file is too small to contain valid RTP information: #{file}.")
139
+ else
140
+ str = File.open(file, 'rb:ISO8859-1') { |f| f.read }
141
+ end
142
+ end
143
+ end
144
+ end
145
+ # Parse the file contents and create the RTP instance:
146
+ if str
147
+ rtp = self.parse(str, options)
148
+ else
149
+ raise "An RTP::Plan object could not be created from the specified file. Check the log for more details."
150
+ end
151
+ return rtp
152
+ end
153
+
154
+ # Creates a new Plan.
155
+ #
156
+ def initialize
157
+ super('PLAN_DEF', 10, 28)
158
+ @current_parent = self
159
+ # Child records:
160
+ @extended_plan = nil
161
+ @prescriptions = Array.new
162
+ @dose_trackings = Array.new
163
+ # No parent (by definition) for the Plan record:
164
+ @parent = nil
165
+ @attributes = [
166
+ # Required:
167
+ :keyword,
168
+ :patient_id,
169
+ :patient_last_name,
170
+ :patient_first_name,
171
+ :patient_middle_initial,
172
+ :plan_id,
173
+ :plan_date,
174
+ :plan_time,
175
+ :course_id,
176
+ # Optional:
177
+ :diagnosis,
178
+ :md_last_name,
179
+ :md_first_name,
180
+ :md_middle_initial,
181
+ :md_approve_last_name,
182
+ :md_approve_first_name,
183
+ :md_approve_middle_initial,
184
+ :phy_approve_last_name,
185
+ :phy_approve_first_name,
186
+ :phy_approve_middle_initial,
187
+ :author_last_name,
188
+ :author_first_name,
189
+ :author_middle_initial,
190
+ :rtp_mfg,
191
+ :rtp_model,
192
+ :rtp_version,
193
+ :rtp_if_protocol,
194
+ :rtp_if_version
195
+ ]
196
+ end
197
+
198
+ # Checks for equality.
199
+ #
200
+ # Other and self are considered equivalent if they are
201
+ # of compatible types and their attributes are equivalent.
202
+ #
203
+ # @param other an object to be compared with self.
204
+ # @return [Boolean] true if self and other are considered equivalent
205
+ #
206
+ def ==(other)
207
+ if other.respond_to?(:to_plan)
208
+ other.send(:state) == state
209
+ end
210
+ end
211
+
212
+ alias_method :eql?, :==
213
+
214
+ # Adds a dose tracking record to this instance.
215
+ #
216
+ # @param [DoseTracking] child a DoseTracking instance which is to be associated with self
217
+ #
218
+ def add_dose_tracking(child)
219
+ @dose_trackings << child.to_dose_tracking
220
+ child.parent = self
221
+ end
222
+
223
+ # Adds an extended plan record to this instance.
224
+ #
225
+ # @param [ExtendedPlan] child an ExtendedPlan instance which is to be associated with self
226
+ #
227
+ def add_extended_plan(child)
228
+ @extended_plan = child.to_extended_plan
229
+ child.parent = self
230
+ end
231
+
232
+ # Adds a prescription site record to this instance.
233
+ #
234
+ # @param [Prescription] child a Prescription instance which is to be associated with self
235
+ #
236
+ def add_prescription(child)
237
+ @prescriptions << child.to_prescription
238
+ child.parent = self
239
+ end
240
+
241
+ # Collects the child records of this instance in a properly sorted array.
242
+ #
243
+ # @return [Array<Prescription, DoseTracking>] a sorted array of self's child records
244
+ #
245
+ def children
246
+ return [@extended_plan, @prescriptions, @dose_trackings].flatten.compact
247
+ end
248
+
249
+ # Removes the reference of the given instance from this instance.
250
+ #
251
+ # @param [ExtendedPlan, Prescription, DoseTracking] record a child record to be removed from this instance
252
+ #
253
+ def delete(record)
254
+ case record
255
+ when Prescription
256
+ delete_child(:prescriptions, record)
257
+ when DoseTracking
258
+ delete_child(:dose_trackings, record)
259
+ when ExtendedPlan
260
+ delete_extended_plan
261
+ else
262
+ logger.warn("Unknown class (record) given to Plan#delete: #{record.class}")
263
+ end
264
+ end
265
+
266
+ # Removes all dose_tracking references from this instance.
267
+ #
268
+ def delete_dose_trackings
269
+ delete_children(:dose_trackings)
270
+ end
271
+
272
+ # Removes the extended plan reference from this instance.
273
+ #
274
+ def delete_extended_plan
275
+ delete_child(:extended_plan)
276
+ end
277
+
278
+ # Removes all prescription references from this instance.
279
+ #
280
+ def delete_prescriptions
281
+ delete_children(:prescriptions)
282
+ end
283
+
284
+ # Computes a hash code for this object.
285
+ #
286
+ # @note Two objects with the same attributes will have the same hash code.
287
+ #
288
+ # @return [Fixnum] the object's hash code
289
+ #
290
+ def hash
291
+ state.hash
292
+ end
293
+
294
+ # Returns self.
295
+ #
296
+ # @return [Plan] self
297
+ #
298
+ def to_plan
299
+ self
300
+ end
301
+
302
+ # Returns self.
303
+ #
304
+ # @return [Plan] self
305
+ #
306
+ def to_rtp
307
+ self
308
+ end
309
+
310
+ # Writes the Plan object, along with its hiearchy of child objects,
311
+ # to a properly formatted RTPConnect ascii file.
312
+ #
313
+ # @param [String] file a path/file string
314
+ # @param [Hash] options an optional hash parameter
315
+ # @option options [Float] :version the Mosaiq compatibility version number (e.g. 2.4) used for the output
316
+ #
317
+ def write(file, options={})
318
+ f = open_file(file)
319
+ f.write(to_s(options))
320
+ f.close
321
+ end
322
+
323
+ # Sets the patient_id attribute.
324
+ #
325
+ # @param [nil, #to_s] value the new attribute value
326
+ #
327
+ def patient_id=(value)
328
+ @patient_id = value && value.to_s
329
+ end
330
+
331
+ # Sets the patient_last_name attribute.
332
+ #
333
+ def patient_last_name=(value)
334
+ @patient_last_name = value && value.to_s
335
+ end
336
+
337
+ # Sets the patient_first_name attribute.
338
+ #
339
+ # @param [nil, #to_s] value the new attribute value
340
+ #
341
+ def patient_first_name=(value)
342
+ @patient_first_name = value && value.to_s
343
+ end
344
+
345
+ # Sets the patient_middle_initial attribute.
346
+ #
347
+ # @param [nil, #to_s] value the new attribute value
348
+ #
349
+ def patient_middle_initial=(value)
350
+ @patient_middle_initial = value && value.to_s
351
+ end
352
+
353
+ # Sets the plan_id attribute.
354
+ #
355
+ # @param [nil, #to_s] value the new attribute value
356
+ #
357
+ def plan_id=(value)
358
+ @plan_id = value && value.to_s
359
+ end
360
+
361
+ # Sets the plan_date attribute.
362
+ #
363
+ # @param [nil, #to_s] value the new attribute value
364
+ #
365
+ def plan_date=(value)
366
+ @plan_date = value && value.to_s
367
+ end
368
+
369
+ # Sets the plan_time attribute.
370
+ #
371
+ # @param [nil, #to_s] value the new attribute value
372
+ #
373
+ def plan_time=(value)
374
+ @plan_time = value && value.to_s
375
+ end
376
+
377
+ # Sets the course_id attribute.
378
+ #
379
+ # @param [nil, #to_s] value the new attribute value
380
+ #
381
+ def course_id=(value)
382
+ @course_id = value && value.to_s
383
+ end
384
+
385
+ # Sets the diagnosis attribute.
386
+ #
387
+ # @param [nil, #to_s] value the new attribute value
388
+ #
389
+ def diagnosis=(value)
390
+ @diagnosis = value && value.to_s
391
+ end
392
+
393
+ # Sets the md_last_name attribute.
394
+ #
395
+ # @param [nil, #to_s] value the new attribute value
396
+ #
397
+ def md_last_name=(value)
398
+ @md_last_name = value && value.to_s
399
+ end
400
+
401
+ # Sets the md_first_name attribute.
402
+ #
403
+ # @param [nil, #to_s] value the new attribute value
404
+ #
405
+ def md_first_name=(value)
406
+ @md_first_name = value && value.to_s
407
+ end
408
+
409
+ # Sets the md_middle_initial attribute.
410
+ #
411
+ # @param [nil, #to_s] value the new attribute value
412
+ #
413
+ def md_middle_initial=(value)
414
+ @md_middle_initial = value && value.to_s
415
+ end
416
+
417
+ # Sets the md_approve_last_name attribute.
418
+ #
419
+ # @param [nil, #to_s] value the new attribute value
420
+ #
421
+ def md_approve_last_name=(value)
422
+ @md_approve_last_name = value && value.to_s
423
+ end
424
+
425
+ # Sets the md_approve_first_name attribute.
426
+ #
427
+ # @param [nil, #to_s] value the new attribute value
428
+ #
429
+ def md_approve_first_name=(value)
430
+ @md_approve_first_name = value && value.to_s
431
+ end
432
+
433
+ # Sets the md_approve_middle_initial attribute.
434
+ #
435
+ # @param [nil, #to_s] value the new attribute value
436
+ #
437
+ def md_approve_middle_initial=(value)
438
+ @md_approve_middle_initial = value && value.to_s
439
+ end
440
+
441
+ # Sets the phy_approve_last_name attribute.
442
+ #
443
+ # @param [nil, #to_s] value the new attribute value
444
+ #
445
+ def phy_approve_last_name=(value)
446
+ @phy_approve_last_name = value && value.to_s
447
+ end
448
+
449
+ # Sets the phy_approve_first_name attribute.
450
+ #
451
+ # @param [nil, #to_s] value the new attribute value
452
+ #
453
+ def phy_approve_first_name=(value)
454
+ @phy_approve_first_name = value && value.to_s
455
+ end
456
+
457
+ # Sets the phy_approve_middle_initial attribute.
458
+ #
459
+ # @param [nil, #to_s] value the new attribute value
460
+ #
461
+ def phy_approve_middle_initial=(value)
462
+ @phy_approve_middle_initial = value && value.to_s
463
+ end
464
+
465
+ # Sets the author_last_name attribute.
466
+ #
467
+ # @param [nil, #to_s] value the new attribute value
468
+ #
469
+ def author_last_name=(value)
470
+ @author_last_name = value && value.to_s
471
+ end
472
+
473
+ # Sets the author_first_name attribute.
474
+ #
475
+ # @param [nil, #to_s] value the new attribute value
476
+ #
477
+ def author_first_name=(value)
478
+ @author_first_name = value && value.to_s
479
+ end
480
+
481
+ # Sets the author_middle_initial attribute.
482
+ #
483
+ # @param [nil, #to_s] value the new attribute value
484
+ #
485
+ def author_middle_initial=(value)
486
+ @author_middle_initial = value && value.to_s
487
+ end
488
+
489
+ # Sets the rtp_mfg attribute.
490
+ #
491
+ # @param [nil, #to_s] value the new attribute value
492
+ #
493
+ def rtp_mfg=(value)
494
+ @rtp_mfg = value && value.to_s
495
+ end
496
+
497
+ # Sets the rtp_model attribute.
498
+ #
499
+ # @param [nil, #to_s] value the new attribute value
500
+ #
501
+ def rtp_model=(value)
502
+ @rtp_model = value && value.to_s
503
+ end
504
+
505
+ # Sets the rtp_version attribute.
506
+ #
507
+ # @param [nil, #to_s] value the new attribute value
508
+ #
509
+ def rtp_version=(value)
510
+ @rtp_version = value && value.to_s
511
+ end
512
+
513
+ # Sets the rtp_if_protocol attribute.
514
+ #
515
+ # @param [nil, #to_s] value the new attribute value
516
+ #
517
+ def rtp_if_protocol=(value)
518
+ @rtp_if_protocol = value && value.to_s
519
+ end
520
+
521
+ # Sets the rtp_if_version attribute.
522
+ #
523
+ # @param [nil, #to_s] value the new attribute value
524
+ #
525
+ def rtp_if_version=(value)
526
+ @rtp_if_version = value && value.to_s
527
+ end
528
+
529
+
530
+ private
531
+
532
+
533
+ # Creates a control point record from the given string.
534
+ #
535
+ # @param [String] string a string line containing a control point definition
536
+ #
537
+ def control_point(string)
538
+ cp = ControlPoint.load(string, @current_parent)
539
+ @current_parent = cp
540
+ end
541
+
542
+ # Creates a dose tracking record from the given string.
543
+ #
544
+ # @param [String] string a string line containing a dose tracking definition
545
+ #
546
+ def dose_tracking(string)
547
+ dt = DoseTracking.load(string, @current_parent)
548
+ @current_parent = dt
549
+ end
550
+
551
+ # Creates an extended plan record from the given string.
552
+ #
553
+ # @param [String] string a string line containing an extended plan definition
554
+ #
555
+ def extended_plan_def(string)
556
+ ep = ExtendedPlan.load(string, @current_parent)
557
+ @current_parent = ep
558
+ end
559
+
560
+ # Creates an extended treatment field record from the given string.
561
+ #
562
+ # @param [String] string a string line containing an extended treatment field definition
563
+ #
564
+ def extended_treatment_field(string)
565
+ ef = ExtendedField.load(string, @current_parent)
566
+ @current_parent = ef
567
+ end
568
+
569
+ # Tests if the path/file is writable, creates any folders if necessary, and opens the file for writing.
570
+ #
571
+ # @param [String] file a path/file string
572
+ # @raise if the given file cannot be created
573
+ #
574
+ def open_file(file)
575
+ # Check if file already exists:
576
+ if File.exist?(file)
577
+ # Is (the existing file) writable?
578
+ unless File.writable?(file)
579
+ raise "The program does not have permission or resources to create this file: #{file}"
580
+ end
581
+ else
582
+ # File does not exist.
583
+ # Check if this file's path contains a folder that does not exist, and therefore needs to be created:
584
+ folders = file.split(File::SEPARATOR)
585
+ if folders.length > 1
586
+ # Remove last element (which should be the file string):
587
+ folders.pop
588
+ path = folders.join(File::SEPARATOR)
589
+ # Check if this path exists:
590
+ unless File.directory?(path)
591
+ # We need to create (parts of) this path:
592
+ require 'fileutils'
593
+ FileUtils.mkdir_p(path)
594
+ end
595
+ end
596
+ end
597
+ # It has been verified that the file can be created:
598
+ return File.new(file, 'wb:ISO8859-1')
599
+ end
600
+
601
+ # Creates a prescription site record from the given string.
602
+ #
603
+ # @param [String] string a string line containing a prescription site definition
604
+ #
605
+ def prescription_site(string)
606
+ p = Prescription.load(string, @current_parent)
607
+ @current_parent = p
608
+ end
609
+
610
+ # Creates a site setup record from the given string.
611
+ #
612
+ # @param [String] string a string line containing a site setup definition
613
+ #
614
+ def site_setup(string)
615
+ s = SiteSetup.load(string, @current_parent)
616
+ @current_parent = s
617
+ end
618
+
619
+ # Collects the attributes of this instance.
620
+ #
621
+ # @note The CRC is not considered part of the attributes of interest and is excluded
622
+ # @return [Array<String>] an array of attributes
623
+ #
624
+ alias_method :state, :values
625
+
626
+ # Creates a treatment field record from the given string.
627
+ #
628
+ # @param [String] string a string line containing a treatment field definition
629
+ #
630
+ def treatment_field(string)
631
+ f = Field.load(string, @current_parent)
632
+ @current_parent = f
633
+ end
634
+
635
+ # Creates a simulation field record from the given string.
636
+ #
637
+ # @param [String] string a string line containing a simulation field definition
638
+ #
639
+ def simulation_field(string)
640
+ sf = SimulationField.load(string, @current_parent)
641
+ @current_parent = sf
642
+ end
643
+
644
+ end
645
+
637
646
  end