test-patient-generator 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +15 -0
- data/Rakefile +18 -0
- data/lib/test-patient-generator.rb +18 -0
- data/lib/tpg/ext/coded.rb +40 -0
- data/lib/tpg/ext/conjunction.rb +23 -0
- data/lib/tpg/ext/data_criteria.rb +159 -0
- data/lib/tpg/ext/derivation_operator.rb +49 -0
- data/lib/tpg/ext/population_criteria.rb +12 -0
- data/lib/tpg/ext/precondition.rb +23 -0
- data/lib/tpg/ext/range.rb +144 -0
- data/lib/tpg/ext/record.rb +6 -0
- data/lib/tpg/ext/subset_operator.rb +7 -0
- data/lib/tpg/ext/temporal_reference.rb +66 -0
- data/lib/tpg/ext/value.rb +112 -0
- data/lib/tpg/generation/exporter.rb +118 -0
- data/lib/tpg/generation/generator.rb +169 -0
- data/lib/tpg/generation/randomizer.rb +255 -0
- data/public/cda.xsl +1 -2
- metadata +66 -0
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem 'hqmf-parser', '~> 1.0.2'
|
4
|
+
gem 'hquery-patient-api', '~> 0.3.0'
|
5
|
+
gem 'hqmf2js', '~> 1.0.0'
|
6
|
+
gem 'health-data-standards', '~> 2.1.2'
|
7
|
+
|
8
|
+
gem 'rake'
|
9
|
+
gem 'pry'
|
10
|
+
gem 'pry-nav'
|
11
|
+
gem 'bson_ext'
|
12
|
+
|
13
|
+
group :test do
|
14
|
+
gem 'simplecov'
|
15
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require "simplecov"
|
3
|
+
|
4
|
+
Dir['lib/tasks/*.rake'].sort.each do |ext|
|
5
|
+
load ext
|
6
|
+
end
|
7
|
+
|
8
|
+
Rake::TestTask.new(:test_unit) do |t|
|
9
|
+
t.libs << "test"
|
10
|
+
t.test_files = FileList['test/**/*_test.rb']
|
11
|
+
t.verbose = true
|
12
|
+
end
|
13
|
+
|
14
|
+
task :test => [:test_unit] do
|
15
|
+
system("open coverage/index.html")
|
16
|
+
end
|
17
|
+
|
18
|
+
task :default => [:test]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'hqmf-parser'
|
2
|
+
require 'health-data-standards'
|
3
|
+
|
4
|
+
require_relative 'tpg/ext/coded'
|
5
|
+
require_relative 'tpg/ext/conjunction'
|
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
|
+
require_relative 'tpg/ext/range'
|
11
|
+
require_relative 'tpg/ext/record'
|
12
|
+
require_relative 'tpg/ext/subset_operator'
|
13
|
+
require_relative 'tpg/ext/temporal_reference'
|
14
|
+
require_relative 'tpg/ext/value'
|
15
|
+
|
16
|
+
require_relative 'tpg/generation/generator'
|
17
|
+
require_relative 'tpg/generation/randomizer'
|
18
|
+
require_relative 'tpg/generation/exporter'
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module HQMF
|
2
|
+
class Coded
|
3
|
+
# Select the relevant value set that matches the given OID and generate a hash that can be stored on a Record.
|
4
|
+
# The hash will be of this format: { "code_set_identified" => [code] }
|
5
|
+
#
|
6
|
+
# @param [String] oid The target value set.
|
7
|
+
# @param [Hash] value_sets Value sets that might contain the OID for which we're searching.
|
8
|
+
# @return A Hash of code sets corresponding to the given oid, each containing one randomly selected code.
|
9
|
+
def self.select_codes(oid, value_sets)
|
10
|
+
value_sets = HQMF::Coded.select_value_sets(oid, value_sets)
|
11
|
+
code_sets = {}
|
12
|
+
value_sets["code_sets"].each do |value_set|
|
13
|
+
code_sets[value_set["code_set"]] = [value_set["codes"].first]
|
14
|
+
end
|
15
|
+
code_sets
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.select_value_sets(oid, value_sets)
|
19
|
+
# 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
|
+
index = value_sets.index{|value_set| value_set["oid"] == oid}
|
21
|
+
value_sets = index.nil? ? { "code_sets" => [] } : value_sets[index]
|
22
|
+
value_sets
|
23
|
+
end
|
24
|
+
|
25
|
+
# Select the relevant value set that matches the given OID and generate a hash that can be stored on a Record.
|
26
|
+
# The hash will be of this format: { "codeSystem" => "code_set_identified", "code" => code }
|
27
|
+
#
|
28
|
+
# @param [String] oid The target value set.
|
29
|
+
# @param [Hash] value_sets Value sets that might contain the OID for which we're searching.
|
30
|
+
# @return A Hash including a code and code system containing one randomly selected code.
|
31
|
+
def self.select_code(oid, value_sets)
|
32
|
+
codes = select_codes(oid, value_sets)
|
33
|
+
codeSystem = codes.keys()[0]
|
34
|
+
{
|
35
|
+
'codeSystem' => codeSystem,
|
36
|
+
'code' => codes[codeSystem][0]
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Conjunction
|
2
|
+
module AllTrue
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# @param [Array] base_patients
|
6
|
+
# @return
|
7
|
+
def generate(base_patients)
|
8
|
+
preconditions.each do |precondition|
|
9
|
+
precondition.generate(base_patients)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# @param [Array] base_patients
|
17
|
+
# @return
|
18
|
+
module AtLeastOneTrue
|
19
|
+
def generate(base_patients)
|
20
|
+
preconditions.sample.generate(base_patients)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module HQMF
|
2
|
+
class DataCriteria
|
3
|
+
attr_accessor :generation_range, :values
|
4
|
+
|
5
|
+
# Generate all acceptable ranges of values and times for this data criteria. These ranges will then be updated by the permutate function
|
6
|
+
# and passed to modify_patient to actually augment the base_patients Records.
|
7
|
+
#
|
8
|
+
# @param [Array] base_patients The list of patients who will be augmented by this data criteria.
|
9
|
+
# @return The updated list of patients. The array will be modified by reference already so this is just for potential convenience.
|
10
|
+
def generate(base_patients)
|
11
|
+
acceptable_times = []
|
12
|
+
|
13
|
+
# Evaluate all of the temporal restrictions on this data criteria.
|
14
|
+
unless temporal_references.nil?
|
15
|
+
# Generate for patients based on each reference and merge the potential times together.
|
16
|
+
temporal_references.each do |reference|
|
17
|
+
acceptable_time = reference.generate(base_patients)
|
18
|
+
acceptable_times = DerivationOperator.intersection(acceptable_time, acceptable_times)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Apply any subset operators (e.g. FIRST)
|
23
|
+
# e.g., if the subset operator is THIRD we need to make at least three entries
|
24
|
+
unless subset_operators.nil?
|
25
|
+
subset_operators.each do |subset_operator|
|
26
|
+
subset_operator.generate(base_patients)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Apply any derivation operator (e.g. UNION)
|
31
|
+
unless derivation_operator.nil?
|
32
|
+
Range.merge(DerivationOperator.generate(base_patients, children_criteria, derivation_operator), acceptable_times)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Set the acceptable ranges for this data criteria so any parents can read it
|
36
|
+
@generation_range = acceptable_times
|
37
|
+
|
38
|
+
# Calculate value information
|
39
|
+
acceptable_values = []
|
40
|
+
acceptable_values << value
|
41
|
+
|
42
|
+
# Walk through all acceptable time/value combinations and alter out patients
|
43
|
+
base_patients.each do |patient|
|
44
|
+
acceptable_times.each do |time|
|
45
|
+
acceptable_values.each do |value|
|
46
|
+
modify_patient(patient, time, Generator.value_sets)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
base_patients
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
#
|
56
|
+
# @param [Array] acceptable_times
|
57
|
+
# @param [Array] acceptable_values
|
58
|
+
def permutate(acceptable_times, acceptable_values)
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
# Modify a Record with this data criteria. Acceptable times and values are defined prior to this function.
|
63
|
+
#
|
64
|
+
# @param [Record] patient The Record that is being modified.
|
65
|
+
# @param [Range] time An acceptable range of times for the coded entry being put on this patient.
|
66
|
+
# @param [Hash] value_sets The value sets that this data criteria references.
|
67
|
+
# @return The modified patient. The passed in patient object will be modified by reference already so this is just for potential convenience.
|
68
|
+
def modify_patient(patient, time, value_sets)
|
69
|
+
# Figure out what kind of data criteria we're looking at
|
70
|
+
if type == :characteristic and property != nil and patient_api_function == nil
|
71
|
+
# We have a special case on our hands.
|
72
|
+
if property == :birthtime
|
73
|
+
patient.birthdate = time.low.to_seconds
|
74
|
+
elsif value.present? && value.system == "Gender"
|
75
|
+
patient.gender = value.code
|
76
|
+
patient.first = Randomizer.randomize_first_name(value.code)
|
77
|
+
elsif property == :clinicalTrialParticipant
|
78
|
+
patient.clinicalTrialParticipant = true
|
79
|
+
end
|
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
|
+
entry = entry_type.classify.constantize.new
|
84
|
+
entry.description = "#{description} (Code List: #{code_list_id})"
|
85
|
+
entry.start_time = time.low.to_seconds if time.low
|
86
|
+
entry.end_time = time.high.to_seconds if time.high
|
87
|
+
entry.status = status
|
88
|
+
entry.codes = Coded.select_codes(code_list_id, value_sets)
|
89
|
+
entry.oid = HQMF::DataCriteria.template_id_for_definition(definition, status, negation)
|
90
|
+
|
91
|
+
# If the value itself has a code, it will be a Coded type. Otherwise, it's just a regular value with a unit.
|
92
|
+
if value.present? && !value.is_a?(AnyValue)
|
93
|
+
entry.values ||= []
|
94
|
+
if value.type == "CD"
|
95
|
+
entry.values << CodedResultValue.new({codes: Coded.select_codes(value.code_list_id, value_sets)})
|
96
|
+
else
|
97
|
+
entry.values << PhysicalQuantityResultValue.new(value.format)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if values.present?
|
102
|
+
entry.values ||= []
|
103
|
+
values.each do |value|
|
104
|
+
if value.type == "CD"
|
105
|
+
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']})
|
106
|
+
else
|
107
|
+
entry.values << PhysicalQuantityResultValue.new(value.format)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Choose a code from each relevant code vocabulary for this entry's negation, if it is negated and referenced.
|
113
|
+
if negation && negation_code_list_id.present?
|
114
|
+
entry.negation_ind = true
|
115
|
+
entry.negation_reason = Coded.select_code(negation_code_list_id, value_sets)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Additional fields (e.g. ordinality, severity, etc) seem to all be special cases. Capture them here.
|
119
|
+
if field_values.present?
|
120
|
+
field_values.each do |name, field|
|
121
|
+
next if field.nil?
|
122
|
+
|
123
|
+
# These fields are sometimes Coded and sometimes Values.
|
124
|
+
if field.type == "CD"
|
125
|
+
codes = Coded.select_codes(field.code_list_id, value_sets)
|
126
|
+
elsif field.type == "IVL_PQ"
|
127
|
+
value = field.format
|
128
|
+
end
|
129
|
+
|
130
|
+
case name
|
131
|
+
when "ORDINAL"
|
132
|
+
entry.ordinality_code = codes
|
133
|
+
when "FACILITY_LOCATION"
|
134
|
+
entry.facility = Facility.new("name" => field.title, "codes" => codes)
|
135
|
+
when "CUMULATIVE_MEDICATION_DURATION"
|
136
|
+
entry.cumulative_medication_duration = value
|
137
|
+
when "SEVERITY"
|
138
|
+
entry.severity = codes
|
139
|
+
when "REASON"
|
140
|
+
|
141
|
+
when "SOURCE"
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Figure out which section this entry will be added to. Some entry names don't map prettily to section names.
|
148
|
+
section_map = { "lab_results" => "results" }
|
149
|
+
section_name = section_map[entry_type]
|
150
|
+
section_name ||= entry_type
|
151
|
+
# Add the updated section to this patient.
|
152
|
+
section = patient.send(section_name)
|
153
|
+
section.push(entry)
|
154
|
+
|
155
|
+
patient
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module HQMF
|
2
|
+
class DerivationOperator
|
3
|
+
# Perform an intersection between two sets of Ranges (assuming these are timestamps).
|
4
|
+
#
|
5
|
+
# @param [Array] set1 One array of Ranges to be intersected.
|
6
|
+
# @param [Array] set2 The other array of Ranges to be intersected.
|
7
|
+
# @return A new array that contains the shared Ranges between set1 and set2.
|
8
|
+
def self.intersection(set1, set2)
|
9
|
+
# Special cases to account for emptiness
|
10
|
+
return [] if set1.empty? && set2.empty?
|
11
|
+
return set1 if set2.empty?
|
12
|
+
return set2 if set1.empty?
|
13
|
+
|
14
|
+
# Merge each element of the two sets together
|
15
|
+
result = []
|
16
|
+
set1.each do |range1|
|
17
|
+
set2.each do |range2|
|
18
|
+
intersect = range1.intersection(range2)
|
19
|
+
result << intersect unless intersect.nil?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
# Perform a union between two sets of Ranges (assuming these are timestamps)
|
27
|
+
#
|
28
|
+
# @param [Array] set1 One array of Ranges to be unioned.
|
29
|
+
# @param [Array] set2 The other array of Ranges to be unioned.
|
30
|
+
# @return A new array tha contains the union of Ranges between set1 and set2.
|
31
|
+
def self.union(set1, set2)
|
32
|
+
# Special cases to account for emptiness
|
33
|
+
return [] if set1.empty? && set2.empty?
|
34
|
+
return set1 if set2.empty?
|
35
|
+
return set2 if set1.empty?
|
36
|
+
|
37
|
+
# Join each element of the two sets together
|
38
|
+
result = []
|
39
|
+
set1.each do |range1|
|
40
|
+
set2.each do |range2|
|
41
|
+
union = range1.union(range2)
|
42
|
+
result.concat!(union)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module HQMF
|
2
|
+
class PopulationCriteria
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# @param [Array] base_patients
|
6
|
+
# @return
|
7
|
+
def generate(base_patients)
|
8
|
+
# All population criteria begin with a single conjunction precondition
|
9
|
+
preconditions.first.generate(base_patients)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module HQMF
|
2
|
+
class Precondition
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# @param [Array] base_patients
|
6
|
+
# @return
|
7
|
+
def generate(base_patients)
|
8
|
+
# d = HQMF::Generator.hqmf.data_criteria(preconditions[2].preconditions[1].preconditions.first.reference.id)
|
9
|
+
|
10
|
+
if conjunction?
|
11
|
+
# Include the matching module to override our generation functions
|
12
|
+
conjunction_module = "Conjunction::#{self.conjunction_code.classify}"
|
13
|
+
conjunction_module = conjunction_module.split('::').inject(Kernel) {|scope, name| scope.const_get(name)}
|
14
|
+
|
15
|
+
extend conjunction_module
|
16
|
+
generate(base_patients)
|
17
|
+
elsif reference
|
18
|
+
data_criteria = HQMF::Generator.hqmf.data_criteria(reference.id)
|
19
|
+
data_criteria.generate(base_patients)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module HQMF
|
2
|
+
class Range
|
3
|
+
# Perform a deep copy of this Range.
|
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
|
+
|
10
|
+
#
|
11
|
+
def format
|
12
|
+
if low
|
13
|
+
low.format
|
14
|
+
elsif high
|
15
|
+
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
|
+
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
|
+
end
|
143
|
+
end
|
144
|
+
end
|