test-patient-generator 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +6 -8
- data/lib/test-patient-generator.rb +1 -6
- data/lib/tpg/ext/coded.rb +9 -3
- data/lib/tpg/ext/data_criteria.rb +150 -142
- data/lib/tpg/ext/range.rb +2 -132
- data/lib/tpg/ext/value.rb +21 -82
- data/lib/tpg/generation/exporter.rb +14 -32
- data/lib/tpg/generation/generator.rb +158 -84
- data/lib/tpg/generation/randomizer.rb +17 -30
- metadata +83 -9
- data/lib/tpg/ext/conjunction.rb +0 -23
- data/lib/tpg/ext/derivation_operator.rb +0 -49
- data/lib/tpg/ext/population_criteria.rb +0 -12
- data/lib/tpg/ext/precondition.rb +0 -23
- data/lib/tpg/ext/subset_operator.rb +0 -7
- data/lib/tpg/ext/temporal_reference.rb +0 -66
data/Gemfile
CHANGED
@@ -1,15 +1,13 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
|
3
|
-
|
4
|
-
gem 'hquery-patient-api', '~> 0.3.0'
|
5
|
-
gem 'hqmf2js', '~> 1.0.1'
|
6
|
-
gem 'health-data-standards', '~> 2.1.4'
|
3
|
+
gemspec
|
7
4
|
|
8
5
|
gem 'rake'
|
9
|
-
gem 'pry'
|
10
|
-
gem 'pry-nav'
|
11
|
-
gem 'bson_ext'
|
12
6
|
|
13
7
|
group :test do
|
14
8
|
gem 'simplecov'
|
15
|
-
|
9
|
+
gem 'turn'
|
10
|
+
gem 'pry'
|
11
|
+
gem 'pry-nav'
|
12
|
+
gem 'pry-stack_explorer'
|
13
|
+
end
|
@@ -1,16 +1,11 @@
|
|
1
1
|
require 'hqmf-parser'
|
2
2
|
require 'health-data-standards'
|
3
|
+
require 'qrda_generator'
|
3
4
|
|
4
5
|
require_relative 'tpg/ext/coded'
|
5
|
-
require_relative 'tpg/ext/conjunction'
|
6
6
|
require_relative 'tpg/ext/data_criteria'
|
7
|
-
require_relative 'tpg/ext/derivation_operator'
|
8
|
-
require_relative 'tpg/ext/population_criteria'
|
9
|
-
require_relative 'tpg/ext/precondition'
|
10
7
|
require_relative 'tpg/ext/range'
|
11
8
|
require_relative 'tpg/ext/record'
|
12
|
-
require_relative 'tpg/ext/subset_operator'
|
13
|
-
require_relative 'tpg/ext/temporal_reference'
|
14
9
|
require_relative 'tpg/ext/value'
|
15
10
|
|
16
11
|
require_relative 'tpg/generation/generator'
|
data/lib/tpg/ext/coded.rb
CHANGED
@@ -15,6 +15,11 @@ module HQMF
|
|
15
15
|
code_sets
|
16
16
|
end
|
17
17
|
|
18
|
+
# Filter through a list of value sets and choose only the ones marked with a given OID.
|
19
|
+
#
|
20
|
+
# @param [String] oid The OID being used for filtering.
|
21
|
+
# @param [Array] value_sets A pool of available value sets
|
22
|
+
# @return The value set from the list with the requested OID.
|
18
23
|
def self.select_value_sets(oid, value_sets)
|
19
24
|
# Pick the value set for this DataCriteria. If it can't be found, it is an error from the value set source. We'll add the entry without codes for now.
|
20
25
|
index = value_sets.index{|value_set| value_set["oid"] == oid}
|
@@ -30,10 +35,11 @@ module HQMF
|
|
30
35
|
# @return A Hash including a code and code system containing one randomly selected code.
|
31
36
|
def self.select_code(oid, value_sets)
|
32
37
|
codes = select_codes(oid, value_sets)
|
33
|
-
|
38
|
+
code_system = codes.keys()[0]
|
39
|
+
return nil if code_system.nil?
|
34
40
|
{
|
35
|
-
'
|
36
|
-
'code' => codes[
|
41
|
+
'code_system' => code_system,
|
42
|
+
'code' => codes[code_system][0]
|
37
43
|
}
|
38
44
|
end
|
39
45
|
end
|
@@ -1,163 +1,171 @@
|
|
1
1
|
module HQMF
|
2
2
|
class DataCriteria
|
3
|
-
attr_accessor :
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# @param [
|
9
|
-
# @
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
3
|
+
attr_accessor :values
|
4
|
+
|
5
|
+
# Modify a Record with this data criteria.
|
6
|
+
#
|
7
|
+
# @param [Record] patient The Record that is being modified.
|
8
|
+
# @param [Range] time The period of time during which the data criteria happens.
|
9
|
+
# @param [Hash] value_sets The value sets that this data criteria references.
|
10
|
+
# @return The modified patient.
|
11
|
+
def modify_patient(patient, time, value_sets)
|
12
|
+
# Modify the patient with a characteristic if this data criteria defines one
|
13
|
+
modify_patient_with_characteristic(patient, time, value_sets)
|
14
|
+
|
15
|
+
# Otherwise we're dealing with a data criteria that describes a coded entry, so we create it and add it to the patient
|
16
|
+
entry = derive_entry(time, value_sets)
|
17
|
+
modify_entry_with_values(entry, value_sets)
|
18
|
+
modify_entry_with_negation(entry, value_sets)
|
19
|
+
modify_entry_with_fields(entry, value_sets)
|
20
|
+
modify_patient_with_entry(patient, entry)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Modify a Record with a data criteria that describes a patient characteristic.
|
26
|
+
#
|
27
|
+
# @param [Record] patient The Record that is being modified.
|
28
|
+
# @param [Range] time The period of time during which the data criteria happens.
|
29
|
+
# @param [Hash] value_sets The value sets that this data criteria references.
|
30
|
+
# @return The modified patient.
|
31
|
+
def modify_patient_with_characteristic(patient, time, value_sets)
|
32
|
+
return nil unless characteristic?
|
33
|
+
|
34
|
+
if property == :birthtime
|
35
|
+
patient.birthdate = time.low.to_seconds
|
36
|
+
elsif property == :gender
|
37
|
+
gender = value.code
|
38
|
+
patient.gender = gender
|
39
|
+
patient.first = Randomizer.randomize_first_name(gender)
|
40
|
+
elsif property == :clinicalTrialParticipant
|
41
|
+
patient.clinicalTrialParticipant = true
|
42
|
+
elsif property == :expired
|
43
|
+
patient.expired = true
|
44
|
+
patient.deathdate = time.high.to_seconds
|
33
45
|
end
|
34
|
-
|
35
|
-
# Set the acceptable ranges for this data criteria so any parents can read it
|
36
|
-
@generation_range = acceptable_times
|
46
|
+
end
|
37
47
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
+
# Determine the apporpriate coded entry type from this data criteria and create one to match.
|
49
|
+
#
|
50
|
+
# @param [Range] time The period of time during which the entry happens.
|
51
|
+
# @param [Hash] value_sets The value sets that this data criteria references.
|
52
|
+
# @return A coded entry with basic data defined by this data criteria.
|
53
|
+
def derive_entry(time, value_sets)
|
54
|
+
return nil if characteristic?
|
55
|
+
|
56
|
+
entry_type = Generator.classify_entry(patient_api_function)
|
57
|
+
entry = entry_type.classify.constantize.new
|
58
|
+
entry.description = "#{description} (Code List: #{code_list_id})"
|
59
|
+
entry.start_time = time.low.to_seconds if time.low
|
60
|
+
entry.end_time = time.high.to_seconds if time.high
|
61
|
+
entry.status = status
|
62
|
+
entry.codes = Coded.select_codes(code_list_id, value_sets)
|
63
|
+
entry.oid = HQMF::DataCriteria.template_id_for_definition(definition, status, negation)
|
64
|
+
entry
|
65
|
+
end
|
66
|
+
|
67
|
+
# Add any value related data to a coded entry from this data criteria.
|
68
|
+
#
|
69
|
+
# @param [Entry] entry The coded entry that this data criteria is defining.
|
70
|
+
# @param [Hash] value_sets The value sets that this data criteria references.
|
71
|
+
# @return The modified coded entry.
|
72
|
+
def modify_entry_with_values(entry, value_sets)
|
73
|
+
return nil unless entry.present? && values.present?
|
74
|
+
|
75
|
+
# If the value itself has a code, it will be a Coded type. Otherwise, it's just a regular value with a unit.
|
76
|
+
entry.values ||= []
|
77
|
+
values.each do |value|
|
78
|
+
if value.type == "CD"
|
79
|
+
entry.values << CodedResultValue.new({codes: Coded.select_codes(value.code_list_id, value_sets), description: HQMF::Coded.select_value_sets(value.code_list_id, value_sets)["concept"]})
|
80
|
+
else
|
81
|
+
entry.values << PhysicalQuantityResultValue.new(value.format)
|
48
82
|
end
|
49
83
|
end
|
50
|
-
|
51
|
-
base_patients
|
52
84
|
end
|
53
|
-
|
54
|
-
#
|
85
|
+
|
86
|
+
# Mark a coded entry as negated if this data criteria describes it as such.
|
55
87
|
#
|
56
|
-
# @param [
|
57
|
-
# @param [
|
58
|
-
|
88
|
+
# @param [Entry] entry The coded entry that this data criteria is potentially negating.
|
89
|
+
# @param [Hash] value_sets The value sets that this data criteria references.
|
90
|
+
# @return The modified coded entry.
|
91
|
+
def modify_entry_with_negation(entry, value_sets)
|
92
|
+
return nil unless entry.present? && negation && negation_code_list_id.present?
|
59
93
|
|
94
|
+
entry.negation_ind = true
|
95
|
+
entry.negation_reason = Coded.select_code(negation_code_list_id, value_sets)
|
60
96
|
end
|
61
|
-
|
62
|
-
#
|
97
|
+
|
98
|
+
# Add this data criteria's field related data to a coded entry.
|
63
99
|
#
|
64
|
-
# @param [
|
65
|
-
# @param [Range] time An acceptable range of times for the coded entry being put on this patient.
|
100
|
+
# @param [Entry] entry The coded entry that this data criteria is modifying.
|
66
101
|
# @param [Hash] value_sets The value sets that this data criteria references.
|
67
|
-
# @return The modified
|
68
|
-
def
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
if
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
else
|
81
|
-
# Otherwise this is a regular coded entry. Start by choosing the correct type and assigning basic metadata.
|
82
|
-
entry_type = Generator.classify_entry(patient_api_function)
|
83
|
-
|
84
|
-
# HACK -- this is here to deal with types that have not been mapped yet and blow things up
|
85
|
-
return patient if entry_type.nil? || entry_type == ""
|
86
|
-
entry = entry_type.classify.constantize.new
|
87
|
-
|
88
|
-
entry.description = "#{description} (Code List: #{code_list_id})"
|
89
|
-
entry.start_time = time.low.to_seconds if time.low
|
90
|
-
entry.end_time = time.high.to_seconds if time.high
|
91
|
-
entry.status = status
|
92
|
-
entry.codes = Coded.select_codes(code_list_id, value_sets)
|
93
|
-
entry.oid = HQMF::DataCriteria.template_id_for_definition(definition, status, negation)
|
94
|
-
|
95
|
-
# If the value itself has a code, it will be a Coded type. Otherwise, it's just a regular value with a unit.
|
96
|
-
if value.present? && !value.is_a?(AnyValue)
|
97
|
-
entry.values ||= []
|
98
|
-
if value.type == "CD"
|
99
|
-
entry.values << CodedResultValue.new({codes: Coded.select_codes(value.code_list_id, value_sets)})
|
100
|
-
else
|
101
|
-
entry.values << PhysicalQuantityResultValue.new(value.format)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
if values.present?
|
106
|
-
entry.values ||= []
|
107
|
-
values.each do |value|
|
108
|
-
if value.type == "CD"
|
109
|
-
entry.values << CodedResultValue.new({codes: Coded.select_codes(value.code_list_id, value_sets), description: Coded.select_value_sets(value.code_list_id, value_sets)['description']})
|
110
|
-
else
|
111
|
-
entry.values << PhysicalQuantityResultValue.new(value.format)
|
112
|
-
end
|
113
|
-
end
|
102
|
+
# @return The modified coded entry.
|
103
|
+
def modify_entry_with_fields(entry, value_sets)
|
104
|
+
return nil unless entry.present? && field_values.present?
|
105
|
+
|
106
|
+
field_values.each do |name, field|
|
107
|
+
next if field.nil?
|
108
|
+
|
109
|
+
# Format the field to be stored in a Record.
|
110
|
+
if field.type == "CD"
|
111
|
+
field_value = Coded.select_code(field.code_list_id, value_sets)
|
112
|
+
field_value["title"] = HQMF::Coded.select_value_sets(field.code_list_id, value_sets)["concept"]
|
113
|
+
else
|
114
|
+
field_value = field.format
|
114
115
|
end
|
115
116
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
117
|
+
field_accessor = nil
|
118
|
+
# Facilities are a special case where we store a whole object on the entry in Record. Create or augment the existing facility with this piece of data.
|
119
|
+
if name.include? "FACILITY"
|
120
|
+
facility = entry.facility
|
121
|
+
facility ||= Facility.new
|
122
|
+
facility_map = {"FACILITY_LOCATION" => :code, "FACILITY_LOCATION_ARRIVAL_DATETIME" => :start_time, "FACILITY_LOCATION_DEPARTURE_DATETIME" => :end_time}
|
123
|
+
|
124
|
+
facility.name = field.title if type == "CD"
|
125
|
+
facility_accessor = facility_map[name]
|
126
|
+
facility.send("#{facility_accessor}=", field_value)
|
127
|
+
|
128
|
+
field_accessor = :facility
|
129
|
+
field_value = facility
|
120
130
|
end
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
value = field.format
|
132
|
-
end
|
133
|
-
|
134
|
-
case name
|
135
|
-
when "ORDINAL"
|
136
|
-
entry.ordinality_code = codes
|
137
|
-
when "FACILITY_LOCATION"
|
138
|
-
entry.facility = Facility.new("name" => field.title, "codes" => codes)
|
139
|
-
when "CUMULATIVE_MEDICATION_DURATION"
|
140
|
-
entry.cumulative_medication_duration = value
|
141
|
-
when "SEVERITY"
|
142
|
-
entry.severity = codes
|
143
|
-
when "REASON"
|
144
|
-
|
145
|
-
when "SOURCE"
|
146
|
-
|
147
|
-
end
|
131
|
+
|
132
|
+
begin
|
133
|
+
field_accessor ||= HQMF::DataCriteria::FIELDS[name][:coded_entry_method]
|
134
|
+
entry.send("#{field_accessor}=", field_value)
|
135
|
+
rescue
|
136
|
+
# Give some feedback if we hit an unexpected error. Some fields have no action expected, so we'll suppress those messages.
|
137
|
+
noop_fields = ["LENGTH_OF_STAY", "START_DATETIME", "STOP_DATETIME"]
|
138
|
+
unless noop_fields.include? name
|
139
|
+
field_accessor = HQMF::DataCriteria::FIELDS[name][:coded_entry_method]
|
140
|
+
puts "Unknown field #{name} was unable to be added via #{field_accessor} to the patient"
|
148
141
|
end
|
149
142
|
end
|
150
|
-
|
151
|
-
# Figure out which section this entry will be added to. Some entry names don't map prettily to section names.
|
152
|
-
section_map = { "lab_results" => "results" }
|
153
|
-
section_name = section_map[entry_type]
|
154
|
-
section_name ||= entry_type
|
155
|
-
# Add the updated section to this patient.
|
156
|
-
section = patient.send(section_name)
|
157
|
-
section.push(entry)
|
158
|
-
|
159
|
-
patient
|
160
143
|
end
|
161
144
|
end
|
145
|
+
|
146
|
+
# Add a coded entry to a patient.
|
147
|
+
#
|
148
|
+
# @param [Record] patient The coded entry that this data criteria is potentially negating.
|
149
|
+
# @param [Entry] entry The value sets that this data criteria references.
|
150
|
+
# @return The modified patient.
|
151
|
+
def modify_patient_with_entry(patient, entry)
|
152
|
+
return patient if entry.nil?
|
153
|
+
|
154
|
+
# Figure out which section this entry will be added to. Some entry names don't map prettily to section names.
|
155
|
+
entry_type = Generator.classify_entry(patient_api_function)
|
156
|
+
section_map = { "lab_results" => "results" }
|
157
|
+
section_name = section_map[entry_type]
|
158
|
+
section_name ||= entry_type
|
159
|
+
|
160
|
+
# Add the updated section to this patient.
|
161
|
+
section = patient.send(section_name)
|
162
|
+
section.push(entry)
|
163
|
+
|
164
|
+
patient
|
165
|
+
end
|
166
|
+
|
167
|
+
def characteristic?
|
168
|
+
type == :characteristic && patient_api_function.nil? ? true : false
|
169
|
+
end
|
162
170
|
end
|
163
171
|
end
|
data/lib/tpg/ext/range.rb
CHANGED
@@ -1,144 +1,14 @@
|
|
1
1
|
module HQMF
|
2
2
|
class Range
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# @return A deep copy of this Range.
|
6
|
-
def clone
|
7
|
-
Range.new(type.try(:clone), low.try(:clone), high.try(:clone), width.try(:clone))
|
8
|
-
end
|
9
|
-
|
3
|
+
# Form an HQMF Range object into a shape that HealthDataStandards understands.
|
10
4
|
#
|
5
|
+
# @return A Range formatted for storing a HealthDataStandards Record.
|
11
6
|
def format
|
12
7
|
if low
|
13
8
|
low.format
|
14
9
|
elsif high
|
15
10
|
high.format
|
16
|
-
else
|
17
|
-
{}
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
# Perform an intersection between this Range and the passed in Range.
|
22
|
-
# There are three potential situations that can happen: disjoint, equivalent, or overlapping.
|
23
|
-
#
|
24
|
-
# @param [Range] range The other Range intersecting this. If it is nil it implies all times are ok (i.e. no restrictions).
|
25
|
-
# @return A new Range that represents the shared amount of time between these two Ranges. nil means there is no common time.
|
26
|
-
def intersection(range)
|
27
|
-
# Return self if nil (the other range has no restrictions) or if it matches the other range (they are equivalent)
|
28
|
-
return self.clone if range.nil?
|
29
|
-
return self.clone if eql?(range)
|
30
|
-
|
31
|
-
# Figure out which range starts later (the more restrictive one)
|
32
|
-
if low <= range.low
|
33
|
-
earlier_start = self
|
34
|
-
later_start = range
|
35
|
-
else
|
36
|
-
earlier_start = range
|
37
|
-
later_start = self
|
38
|
-
end
|
39
|
-
|
40
|
-
# Return nil if there is no common time (the two ranges are entirely disjoint)
|
41
|
-
return nil unless later_start.contains?(earlier_start.high)
|
42
|
-
|
43
|
-
# Figure out which ranges ends earlier (the more restrictive one)
|
44
|
-
if high >= range.high
|
45
|
-
earlier_end = self
|
46
|
-
later_end = range
|
47
|
-
else
|
48
|
-
earlier_end = range
|
49
|
-
later_end = self
|
50
|
-
end
|
51
|
-
|
52
|
-
Range.new("TS", later_start.low.clone, earlier_end.high.clone, nil)
|
53
|
-
end
|
54
|
-
|
55
|
-
# Perform a union between this Range and the passed in Range.
|
56
|
-
# There are three potential situations that can happen: disjoint, equivalent, or overlapping.
|
57
|
-
#
|
58
|
-
# @param [Range] range The other Range unioning this.
|
59
|
-
# @return An array of Ranges. One element if the two ranges are overlapping and can be expressed as one new Range
|
60
|
-
# or two Ranges if the times are disjoint.
|
61
|
-
def union(range)
|
62
|
-
# Return self if nil (nothing new to add) or if it matches the other range (they are equivalent)
|
63
|
-
return self.clone if range.nil?
|
64
|
-
return self.clone if eql?(range)
|
65
|
-
|
66
|
-
# Figure out which range starts earlier (to capture the most time)
|
67
|
-
if low <= range.low
|
68
|
-
earlier_start = self
|
69
|
-
later_start = range
|
70
|
-
else
|
71
|
-
earlier_start = range
|
72
|
-
later_start = self
|
73
|
-
end
|
74
|
-
|
75
|
-
# Figure out which ranges ends earlier (the more restrictive one)
|
76
|
-
if high >= range.high
|
77
|
-
earlier_end = self
|
78
|
-
later_end = range
|
79
|
-
else
|
80
|
-
earlier_end = range
|
81
|
-
later_end = self
|
82
11
|
end
|
83
|
-
|
84
|
-
result = []
|
85
|
-
# We have continuous Ranges so we can return one Range to encapsulate both
|
86
|
-
if earlier_start.contains?(later_start.low)
|
87
|
-
result << Range.new("TS", earlier_start.low.clone, later_end.high.clone, nil)
|
88
|
-
else
|
89
|
-
# The Ranges are disjoint, so we'll need to return two arrays to capture all of the potential times
|
90
|
-
result << Range.new("TS", earlier_start.low.clone, earlier_start.high.clone, nil)
|
91
|
-
result << Range.new("TS", later_start.low.clone, later_start.high.clone, nil)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
#
|
96
|
-
#
|
97
|
-
# @param [Range] ivl_pq
|
98
|
-
# @return
|
99
|
-
def apply_pq(ivl_pq)
|
100
|
-
|
101
|
-
end
|
102
|
-
|
103
|
-
#
|
104
|
-
#
|
105
|
-
# @param [Range] range1
|
106
|
-
# @param [Range] range2
|
107
|
-
# @return
|
108
|
-
def self.merge_ranges(range1, range2)
|
109
|
-
return nil if range1.nil? && range2.nil?
|
110
|
-
return range1 if range2.nil?
|
111
|
-
return range2 if range1.nil?
|
112
|
-
|
113
|
-
type = range1.type == "PQ" && range2.type == "PQ" ? "IVL_PQ" : "IVL_TS"
|
114
|
-
low = Value.merge_values(range1.low, range2.low)
|
115
|
-
high = Value.merge_values(range1.high, range2.high)
|
116
|
-
width = nil
|
117
|
-
|
118
|
-
Range.new(type, low, high, width)
|
119
|
-
end
|
120
|
-
|
121
|
-
# Check to see if a given value falls within this Range's high and low.
|
122
|
-
#
|
123
|
-
# @param [Value] value The value that may or may not fall within the range.
|
124
|
-
# @return True if the value is contained. Otherwise, false.
|
125
|
-
def contains?(value)
|
126
|
-
start_time = low.to_time_object
|
127
|
-
end_time = high.to_time_object
|
128
|
-
time = value.to_time_object
|
129
|
-
|
130
|
-
time.between?(start_time, end_time)
|
131
|
-
end
|
132
|
-
|
133
|
-
# Check to see if a given Range's low and high matches this' low and high.
|
134
|
-
#
|
135
|
-
# @param [Range] range The Range to which we're comparing.
|
136
|
-
# @return True if the given range starts and ends at the same time as this. Otherwise, false.
|
137
|
-
def eql?(range)
|
138
|
-
return false if range.nil? || low.nil? || range.low.nil? || high.nil? || range.high.nil?
|
139
|
-
|
140
|
-
return low.value == range.low.value && low.inclusive? == range.low.inclusive? &&
|
141
|
-
high.value == range.high.value && high.inclusive? == range.high.inclusive?
|
142
12
|
end
|
143
13
|
end
|
144
14
|
end
|