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
@@ -1,11 +1,5 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
1
|
module HQMF
|
4
|
-
# Contains functions that can be used to randomly generate fields for patients.
|
5
|
-
# Also includes utility functions for randomly generating numbers and dates or choosing options within a range.
|
6
2
|
class Randomizer
|
7
|
-
UNIQUE_PATIENT_IDS = Set.new # Used to make sure we don't duplicate randomly generated patient IDs
|
8
|
-
|
9
3
|
# Add trivial demographics info to a Record. Trivial fields are ones that identify a patient as unique to a human eye
|
10
4
|
# but have no impact on CQM, e.g. address. This is essentially an upsert, so any preexisting info will be wiped.
|
11
5
|
#
|
@@ -21,7 +15,7 @@ module HQMF
|
|
21
15
|
patient.languages << randomize_language
|
22
16
|
patient.first = randomize_first_name(patient.gender) if patient.gender
|
23
17
|
patient.last = randomize_last_name
|
24
|
-
patient.medical_record_number =
|
18
|
+
patient.medical_record_number = Digest::MD5.hexdigest("#{patient.first} #{patient.last}")
|
25
19
|
|
26
20
|
patient
|
27
21
|
end
|
@@ -34,10 +28,10 @@ module HQMF
|
|
34
28
|
# Black persons 12.6%
|
35
29
|
# Hispanic 16.3%
|
36
30
|
# White 63.7%
|
37
|
-
def self.randomize_race_and_ethnicity
|
38
|
-
|
31
|
+
def self.randomize_race_and_ethnicity(percent = nil)
|
32
|
+
percent ||= rand(999)
|
39
33
|
|
40
|
-
case
|
34
|
+
case percent
|
41
35
|
when 0..1
|
42
36
|
{race: '2076-8', ethnicity: '2186-5'} # pacific islander
|
43
37
|
when 2..10
|
@@ -73,11 +67,11 @@ module HQMF
|
|
73
67
|
# 00.1% persian
|
74
68
|
# 00.1% us sign
|
75
69
|
# 03.0% other
|
76
|
-
def self.randomize_language
|
77
|
-
|
70
|
+
def self.randomize_language(percent = nil)
|
71
|
+
percent ||= rand(999)
|
78
72
|
|
79
|
-
case
|
80
|
-
when 0..802
|
73
|
+
case percent
|
74
|
+
when 0..802
|
81
75
|
'en-US' # english
|
82
76
|
when 802..925
|
83
77
|
'es-US' # spanish
|
@@ -175,26 +169,17 @@ module HQMF
|
|
175
169
|
'postalCode' => zip
|
176
170
|
}.to_json
|
177
171
|
end
|
178
|
-
|
179
|
-
# Randomize patient IDs for a given patients. We ensure that this ID is unique.
|
180
|
-
#
|
181
|
-
# @return The same patient with a random, non-duplicate ID assigned.
|
182
|
-
def self.randomize_patient_id
|
183
|
-
# Keep trying to add a new ID to the set. Return the ID when we find one that's unique.
|
184
|
-
loop do
|
185
|
-
id = (0...10).map{ ('0'..'9').to_a[rand(10)] }.join.to_s
|
186
|
-
break id if UNIQUE_PATIENT_IDS.size < UNIQUE_PATIENT_IDS.add(id).size
|
187
|
-
end
|
188
|
-
end
|
189
172
|
|
190
173
|
# More accurately, randomize a believable birthdate. Given a patient, find all coded entries that
|
191
174
|
# have age-related implications and make sure the patient is at least that old. Since that is complicated,
|
192
|
-
# for now let's just make them
|
175
|
+
# for now let's just make them 40
|
193
176
|
#
|
194
177
|
# @param A patient with coded entries that dictate potential birthdates
|
195
178
|
# @return A realistic birthdate for the given patient
|
196
179
|
def self.randomize_birthdate(patient)
|
197
|
-
Time.now
|
180
|
+
now = Time.now
|
181
|
+
range = randomize_range(now.advance(years: -40), now.advance(years: -35))
|
182
|
+
range.low.to_time_object
|
198
183
|
end
|
199
184
|
|
200
185
|
# Randomly generate a Range object that is within a lower and upper bounds. It is guaranteed that the high of the
|
@@ -202,12 +187,14 @@ module HQMF
|
|
202
187
|
#
|
203
188
|
# @param [Time] earliest_time The lowest possible value for the low in this Range. nil implies unbounded.
|
204
189
|
# @param [Time] latest_time The highest possible value for the high in this Range. nil implies unbounded.
|
190
|
+
# @param [Hash] maximum_length Optionally used to bound the length of a range. This is a hash that can be passed as a parameter to Time.advance.
|
205
191
|
# @return A Range that represents a start time and end time within the acceptable bounds supplied.
|
206
|
-
def self.randomize_range(earliest_time, latest_time)
|
207
|
-
earliest_time ||= Time.now.advance(years: -
|
208
|
-
latest_time ||= Time.now.
|
192
|
+
def self.randomize_range(earliest_time, latest_time, maximum_length = nil)
|
193
|
+
earliest_time ||= Time.now.advance(years: -35)
|
194
|
+
latest_time ||= Time.now.advance(days: -1)
|
209
195
|
|
210
196
|
low = Time.at(Randomizer.between(earliest_time.to_i, latest_time.to_i))
|
197
|
+
latest_time = low.advance(maximum_length) if maximum_length
|
211
198
|
high = Time.at(Randomizer.between(low, latest_time.to_i))
|
212
199
|
|
213
200
|
low = Value.new("TS", nil, Value.time_to_ts(low), true, false, false)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: test-patient-generator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,88 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
13
|
-
dependencies:
|
12
|
+
date: 2012-12-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: health-data-standards
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.2.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.2.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: hquery-patient-api
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.0.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.0.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: hqmf-parser
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.1.0
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.1.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: hqmf2js
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.1.0
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.1.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: qrda_generator
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.0.1
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.0.1
|
14
94
|
description: A utility to generate patients for unit testing clinical quality measure
|
15
95
|
logic. The instructions for generation are guided by HQMF documents and exported
|
16
96
|
into various health standards, e.g. C32, CCR.
|
@@ -21,15 +101,9 @@ extra_rdoc_files: []
|
|
21
101
|
files:
|
22
102
|
- lib/test-patient-generator.rb
|
23
103
|
- lib/tpg/ext/coded.rb
|
24
|
-
- lib/tpg/ext/conjunction.rb
|
25
104
|
- lib/tpg/ext/data_criteria.rb
|
26
|
-
- lib/tpg/ext/derivation_operator.rb
|
27
|
-
- lib/tpg/ext/population_criteria.rb
|
28
|
-
- lib/tpg/ext/precondition.rb
|
29
105
|
- lib/tpg/ext/range.rb
|
30
106
|
- lib/tpg/ext/record.rb
|
31
|
-
- lib/tpg/ext/subset_operator.rb
|
32
|
-
- lib/tpg/ext/temporal_reference.rb
|
33
107
|
- lib/tpg/ext/value.rb
|
34
108
|
- lib/tpg/generation/exporter.rb
|
35
109
|
- lib/tpg/generation/generator.rb
|
data/lib/tpg/ext/conjunction.rb
DELETED
@@ -1,23 +0,0 @@
|
|
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
|
@@ -1,49 +0,0 @@
|
|
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
|
@@ -1,12 +0,0 @@
|
|
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
|
data/lib/tpg/ext/precondition.rb
DELETED
@@ -1,23 +0,0 @@
|
|
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
|
@@ -1,66 +0,0 @@
|
|
1
|
-
module HQMF
|
2
|
-
# Generates a Range to define the timing of a data_criteria
|
3
|
-
class TemporalReference
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# @param [Array] base_patients
|
7
|
-
# @return
|
8
|
-
def generate(base_patients)
|
9
|
-
if reference.id == "MeasurePeriod"
|
10
|
-
matching_time = Generator::hqmf.measure_period.clone
|
11
|
-
else
|
12
|
-
# First generate patients for the data criteria that this temporal reference points to
|
13
|
-
data_criteria = Generator::hqmf.data_criteria(reference.id)
|
14
|
-
base_patients = data_criteria.generate(base_patients)
|
15
|
-
|
16
|
-
# Now that the data criteria is defined, we can set our relative time to those generated results
|
17
|
-
matching_time = data_criteria.generation_range.first.clone
|
18
|
-
end
|
19
|
-
|
20
|
-
# TODO add nils where necessary. Ranges should be unbounded, despite the relative time's potential bounds (unless the type specifies)
|
21
|
-
if range
|
22
|
-
offset = range.try(:clone)
|
23
|
-
|
24
|
-
case type
|
25
|
-
when "DURING"
|
26
|
-
# TODO differentiate between this and CONCURRENT
|
27
|
-
when "SBS" # Starts before start
|
28
|
-
offset.low.value.insert(0, "-")
|
29
|
-
when "SAS" # Starts after start
|
30
|
-
offset.low = offset.high
|
31
|
-
offset.high = nil
|
32
|
-
when "SBE" # Starts before end
|
33
|
-
offset.low = offset.high
|
34
|
-
offset.high = nil
|
35
|
-
offset.low.value.insert(0, "-")
|
36
|
-
matching_time.low = matching_time.high
|
37
|
-
when "SAE" # Starts after end
|
38
|
-
|
39
|
-
when "EBS" # Ends before start
|
40
|
-
|
41
|
-
when "EAS" # Ends after start
|
42
|
-
|
43
|
-
when "EBE" # Ends before end
|
44
|
-
|
45
|
-
when "EAE" # Ends after end
|
46
|
-
|
47
|
-
when "SDU" # Starts during
|
48
|
-
matching_time.high.value = nil
|
49
|
-
when "EDU" # Ends during
|
50
|
-
|
51
|
-
when "ECW" # Ends concurrent with
|
52
|
-
|
53
|
-
when "SCW" # Starts concurrent with
|
54
|
-
|
55
|
-
when "CONCURRENT"
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
matching_time = Range.merge_ranges(offset, matching_time)
|
60
|
-
end
|
61
|
-
|
62
|
-
# Note we return the possible times to the calling data criteria, not patients
|
63
|
-
[matching_time]
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|