quality-measure-engine 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/Gemfile +9 -9
  2. data/README.md +39 -2
  3. data/Rakefile +25 -44
  4. data/js/map-reduce-utils.js +174 -0
  5. data/js/underscore-min.js +24 -0
  6. data/lib/qme/importer/code_system_helper.rb +26 -0
  7. data/lib/qme/importer/entry.rb +89 -0
  8. data/lib/qme/importer/generic_importer.rb +71 -0
  9. data/lib/qme/importer/hl7_helper.rb +27 -0
  10. data/lib/qme/importer/patient_importer.rb +150 -0
  11. data/lib/qme/importer/property_matcher.rb +103 -0
  12. data/lib/qme/importer/section_importer.rb +82 -0
  13. data/lib/qme/map/map_reduce_builder.rb +77 -147
  14. data/lib/qme/map/map_reduce_executor.rb +97 -13
  15. data/lib/qme/measure/database_loader.rb +90 -0
  16. data/lib/qme/measure/measure_loader.rb +141 -0
  17. data/lib/qme/mongo_helpers.rb +15 -0
  18. data/lib/qme/randomizer/patient_randomizer.rb +95 -0
  19. data/lib/qme_test.rb +13 -0
  20. data/lib/quality-measure-engine.rb +20 -4
  21. data/lib/tasks/measure.rake +76 -0
  22. data/lib/tasks/mongo.rake +74 -0
  23. data/lib/tasks/patient_random.rake +46 -0
  24. metadata +110 -156
  25. data/.gitignore +0 -6
  26. data/Gemfile.lock +0 -44
  27. data/fixtures/complex_measure.json +0 -36
  28. data/fixtures/result_example.json +0 -6
  29. data/lib/patches/v8.rb +0 -20
  30. data/lib/qme/query/json_document_builder.rb +0 -130
  31. data/lib/qme/query/json_query_executor.rb +0 -44
  32. data/measures/0032/0032_NQF_Cervical_Cancer_Screening.json +0 -171
  33. data/measures/0032/patients/denominator1.json +0 -10
  34. data/measures/0032/patients/denominator2.json +0 -10
  35. data/measures/0032/patients/numerator1.json +0 -11
  36. data/measures/0032/patients/population1.json +0 -9
  37. data/measures/0032/patients/population2.json +0 -11
  38. data/measures/0032/result/result.json +0 -6
  39. data/measures/0043/0043_NQF_PneumoniaVaccinationStatusForOlderAdults.json +0 -71
  40. data/measures/0043/patients/denominator.json +0 -11
  41. data/measures/0043/patients/numerator.json +0 -11
  42. data/measures/0043/patients/population.json +0 -10
  43. data/measures/0043/result/result.json +0 -6
  44. data/quality-measure-engine.gemspec +0 -97
  45. data/schema/result.json +0 -28
  46. data/schema/schema.json +0 -143
  47. data/spec/qme/map/map_reduce_builder_spec.rb +0 -64
  48. data/spec/qme/measures_spec.rb +0 -50
  49. data/spec/qme/query/json_document_builder_spec.rb +0 -56
  50. data/spec/schema_spec.rb +0 -21
  51. data/spec/spec_helper.rb +0 -7
  52. data/spec/validate_measures_spec.rb +0 -21
@@ -1,31 +1,115 @@
1
1
  module QME
2
2
  module MapReduce
3
+
4
+ # Computes the value of quality measures based on the current set of patient
5
+ # records in the database
3
6
  class Executor
7
+
8
+ # Create a new Executor for the supplied database connection
4
9
  def initialize(db)
5
10
  @db = db
6
11
  end
7
12
 
8
- def measure_def(measure_id)
13
+ # Retrieve a measure definition from the database
14
+ # @param [String] measure_id value of the measure's id field
15
+ # @param [String] sub_id value of the measure's sub_id field, may be nil for measures with only a single numerator and denominator
16
+ # @return [Hash] a JSON hash of the encoded measure
17
+ def measure_def(measure_id, sub_id=nil)
9
18
  measures = @db.collection('measures')
10
- measures.find({'id'=> "#{measure_id}"}).to_a[0]
19
+ if sub_id
20
+ measures.find_one({'id'=>measure_id, 'sub_id'=>sub_id})
21
+ else
22
+ measures.find_one({'id'=>measure_id})
23
+ end
11
24
  end
12
25
 
13
- def measure_result(measure_id, parameter_values)
26
+ # Compute the specified measure
27
+ # @param [String] measure_id value of the measure's id field
28
+ # @param [String] sub_id value of the measure's sub_id field, may be nil for measures with only a single numerator and denominator
29
+ # @param [Hash] parameter_values a hash of the measure parameter values. Keys may be either a String or Symbol
30
+ # @return [Hash] a hash of the measure result with Symbol keys: population, denominator, numerator, antinumerator and exclusions whose values are the count of patient records that meet the criteria for each component of the measure. Also included are keys: population_members, denominator_members, numerator_members, antinumerator_members and exclusions_members whose values are arrays of the patient record identifiers that meet the criteria for each component of the measure.
31
+ def measure_result(measure_id, sub_id, parameter_values)
14
32
 
15
- measure = Builder.new(measure_def(measure_id), parameter_values)
33
+ cache = @db.collection("query_cache")
34
+ cache_q = {:measure_id=>measure_id, :sub_id=>sub_id,:effective_date=>parameter_values[:effective_date]}
35
+ result = cache.find_one(cache_q)
36
+ unless result
37
+ measure = Builder.new(@db, measure_def(measure_id, sub_id), parameter_values)
16
38
 
17
- records = @db.collection('records')
18
- results = records.map_reduce(measure.map_function, measure.reduce_function)
19
- result = results.find.to_a[0]
39
+ records = @db.collection('records')
40
+ results = records.map_reduce(measure.map_function, measure.reduce_function)
41
+ result = results.find_one # only one key is used by map fn
42
+ result['value']['cache_name'] = cache_name(measure_id,sub_id,parameter_values[:effective_date])
43
+ cache_q["value"] =result['value']
44
+ cache << cache_q
45
+
46
+ cache_measure_patients(measure_id,sub_id,parameter_values[:effective_date],result['value'])
47
+ end
48
+
20
49
  value = result['value']
50
+ summary = {}
51
+ %w(population denominator numerator antinumerator exclusions).each do |field|
52
+ summary[field.intern] = value[field].length
53
+ summary[(field+'_members').intern] = value[field]
54
+ end
55
+ summary
21
56
 
22
- {
23
- :population=>value['i'].to_i,
24
- :denominator=> value['d'].to_i,
25
- :numerator=> value['n'].to_i,
26
- :exceptions=> value['e'].to_i
27
- }
28
57
  end
58
+
59
+ # Return a list of the measures in the database
60
+ # @return [Hash] an hash of measure definitions
61
+ def all_measures
62
+ result = {}
63
+ measures = @db.collection('measures')
64
+ measures.find().each do |measure|
65
+ id = measure['id']
66
+ sub_id = measure['sub_id']
67
+ measure_id = "#{id}#{sub_id}.json"
68
+ result[measure_id] ||= measure
69
+ end
70
+ result
71
+ end
72
+
73
+
74
+ private
75
+
76
+
77
+ def cache_name(measure_id, sub_id, effective_date)
78
+ "cached_measure_patients_#{measure_id}_#{sub_id}_#{effective_date}"
79
+ end
80
+
81
+ # Private method to cache the patient record for a particular measure and effective time. This also caches if they are part of the denominator, numeratore ..... which will be used for sorting.
82
+ # @param [String] measure_id value of the measure's id field
83
+ # @param [String] sub_id value of the measure's sub_id field, may be nil for measures with only a single numerator and denominator
84
+ # @param [Integer] effective_date the effective date used by the map reduce function to calculate popultion, denominator .......
85
+ def cache_measure_patients(measure_id, sub_id, effective_date, results,drop=true)
86
+ col_name =cache_name(measure_id, sub_id, effective_date)
87
+ @db.collection(col_name).drop if drop
88
+ cached_patients = @db.collection(col_name)
89
+ population = results['population']
90
+ if population
91
+ records = @db.collection("records").find('_id' => {'$in' => results['population']})
92
+ records.each do |record|
93
+ record_id = record['_id']
94
+ new_record = {:first=>record['first'],
95
+ :last=>record['last'],
96
+ :birthdate=>record['birthdate'],
97
+ :gender=>record['gender'],
98
+ :measure_id => measure_id,
99
+ :sub_id=>sub_id,
100
+ :effective_date => effective_date,
101
+ :measure_data => record[measure_id],
102
+ :numerator=>!results['numerator'].index(record_id).nil?,
103
+ :denominator=>!results['denominator'].index(record_id).nil?,
104
+ :exclusion=>!results['exclusions'].index(record_id).nil?,
105
+ :antinumerator=>!results['antinumerator'].index(record_id).nil?}
106
+
107
+ cached_patients << new_record
108
+
109
+ end
110
+ end
111
+ end
112
+
29
113
  end
30
114
  end
31
115
  end
@@ -0,0 +1,90 @@
1
+ require File.join(File.dirname(__FILE__),'measure_loader')
2
+
3
+ module QME
4
+ module Database
5
+
6
+ # Utility class for working with JSON files and the database
7
+ class Loader
8
+ # Create a new Loader. Database host and port may be set using the
9
+ # environment variables TEST_DB_HOST and TEST_DB_PORT which default
10
+ # to localhost and 27017 respectively.
11
+ # @param [String] db_name the name of the database to use
12
+ def initialize(db_name)
13
+ @db_name = db_name
14
+ @db_host = ENV['TEST_DB_HOST'] || 'localhost'
15
+ @db_port = ENV['TEST_DB_PORT'] ? ENV['TEST_DB_PORT'].to_i : 27017
16
+ end
17
+
18
+ # Lazily creates a connection to the database and initializes the
19
+ # JavaScript environment
20
+ # @return [Mongo::Connection]
21
+ def get_db
22
+ if @db==nil
23
+ @db = Mongo::Connection.new(@db_host, @db_port).db(@db_name)
24
+ QME::MongoHelpers.initialize_javascript_frameworks(@db)
25
+ end
26
+ @db
27
+ end
28
+
29
+ # Load a measure from the filesystem and save it in the database.
30
+ # @param [String] measure_dir path to the directory containing a measure or measure collection document
31
+ # @param [String] collection_name name of the database collection to save the measure into.
32
+ # @return [Array] the stroed measures as an array of JSON measure hashes
33
+ def save_measure(measure_dir, collection_name)
34
+ measures = QME::Measure::Loader.load_measure(measure_dir)
35
+ measures.each do |measure|
36
+ save(collection_name, measure)
37
+ end
38
+ measures
39
+ end
40
+
41
+ # Save a JSON hash to the specified collection, creates the
42
+ # collection if it doesn't already exist.
43
+ # @param [String] collection_name name of the database collection
44
+ # @param [Hash] json the JSON hash to save in the database
45
+ def save(collection_name, json)
46
+ collection = get_db.create_collection(collection_name)
47
+ collection.save(json)
48
+ end
49
+
50
+ # Drop a database collection
51
+ # @param [String] collection_name name of the database collection
52
+ def drop_collection(collection_name)
53
+ get_db.drop_collection(collection_name)
54
+ end
55
+
56
+
57
+ # Save a bundle to the db, this will save the bundle meta data, javascript extension functions and measures to
58
+ # the db in their respective loacations
59
+ # @param [String] bundle_dir the bundle directory
60
+ # @param [String] bundle_collection the collection to save the bundle meta_data and extension functions to
61
+ # @param [String] measure_collection the collection to save the measures to, defaults to measures
62
+ def save_bundle(bundle_dir,bundle_collection, measure_collection = 'measures')
63
+ bundle = QME::Measure::Loader.load_bundle(bundle_dir)
64
+ bundle[:bundle_data][:measures] = []
65
+ b_id = save(bundle_collection,bundle[:bundle_data])
66
+ measures = bundle[:measures]
67
+ measures.each do |measure|
68
+ measure[:bundle] = b_id
69
+ m_id = save(measure_collection,measure)
70
+ bundle[:bundle_data][:measures] << m_id
71
+ end
72
+ save(bundle_collection,bundle[:bundle_data])
73
+ bundle[:bundle_data][:extensions].each do |ext|
74
+ get_db.eval(ext)
75
+ end
76
+ bundle
77
+ end
78
+
79
+
80
+ def remove_bundle(bundle_id, bundle_collection = 'bundles', measure_collection = 'measures')
81
+ bundle = get_db[bundle_collection].find_one(bundle_id)
82
+ bundle['measures'].each do |measure|
83
+ mes = get_db[measure_collection].find_one(measure)
84
+ get_db[measure_collection].remove(mes)
85
+ end
86
+ get_db[bundle_collection].remove(bundle)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,141 @@
1
+ gem 'rubyzip'
2
+ require 'json'
3
+ require 'zip/zip'
4
+ require 'zip/zipfilesystem'
5
+
6
+ module QME
7
+ module Measure
8
+
9
+ # Utility class for working with JSON measure definition files
10
+ class Loader
11
+
12
+ # Load a measure from the filesystem
13
+ # @param [String] measure_dir path to the directory containing a measure or measure collection document
14
+ # @return [Array] the measures as an array of JSON measure hashes
15
+ def self.load_measure(measure_dir)
16
+ measures = []
17
+ Dir.glob(File.join(measure_dir, '*.col')).each do |collection_file|
18
+ component_dir = File.join(measure_dir, 'components')
19
+ load_collection(collection_file, component_dir).each do |measure|
20
+ measures << measure
21
+ end
22
+ end
23
+ Dir.glob(File.join(measure_dir, '*.json')).each do |measure_file|
24
+ files = Dir.glob(File.join(measure_dir,'*.js'))
25
+ if files.length!=1
26
+ raise "Unexpected number of map functions in #{measure_dir}, expected 1"
27
+ end
28
+ map_file = files[0]
29
+ measure = load_measure_file(measure_file, map_file)
30
+ measures << measure
31
+ end
32
+ measures
33
+ end
34
+
35
+ # Load a collection of measures definitions by processing a collection
36
+ # definition file.
37
+ # @param [String] collection_file path of the collection definition file
38
+ # @param [String] component_dir path to the directory that contains the measure components
39
+ # @return [Array] an array of measure definition JSON hashes
40
+ def self.load_collection(collection_file, component_dir)
41
+ collection_def = JSON.parse(File.read(collection_file))
42
+ measure_file = File.join(component_dir, collection_def['root'])
43
+ measures = []
44
+ collection_def['combinations'].each do |combination|
45
+ map_file = File.join(component_dir, combination['map_fn'])
46
+ measure = load_measure_file(measure_file, map_file)
47
+ # add inline metadata to top level of definition
48
+ combination['metadata'] ||= {}
49
+ combination['metadata'].each do |key, value|
50
+ measure[key] = value
51
+ end
52
+ # add inline measure-specific properties to definition
53
+ combination['measure'] ||= {}
54
+ measure['measure'] ||= {}
55
+ combination['measure'].each do |key, value|
56
+ measure['measure'][key] = value
57
+ end
58
+ ['population', 'denominator', 'numerator', 'exclusions'].each do |component|
59
+ if combination[component]
60
+ measure[component] = load_json(component_dir, combination[component])
61
+ end
62
+ end
63
+ measures << measure
64
+ end
65
+ measures
66
+ end
67
+
68
+ # For ease of development, measure definition JSON files and JavaScript
69
+ # map functions are stored separately in the file system, this function
70
+ # combines the two and returns the result
71
+ # @param [String] measure_file path to the measure file
72
+ # @param [String] map_fn_file path to the map function file
73
+ # @return [Hash] a JSON hash of the measure with embedded map function.
74
+ def self.load_measure_file(measure_file, map_fn_file)
75
+ map_fn = File.read(map_fn_file)
76
+ measure = JSON.parse(File.read(measure_file))
77
+ measure['map_fn'] = map_fn
78
+ measure
79
+ end
80
+
81
+ # Load a JSON file from the specified directory
82
+ # @param [String] dir_path path to the directory containing the JSON file
83
+ # @param [String] filename the JSON file
84
+ # @return [Hash] the parsed JSON hash
85
+ def self.load_json(dir_path, filename)
86
+ file_path = File.join(dir_path, filename)
87
+ JSON.parse(File.read(file_path))
88
+ end
89
+
90
+
91
+ #Load a bundle from a directory
92
+ #@param [String] bundel_path path to directory containing the bundle information
93
+
94
+ def self.load_bundle(bundle_path)
95
+ bundle = {};
96
+ bundle_file = File.join(bundle_path,'bundle.js')
97
+
98
+ bundle[:bundle_data] = File.exists?(bundle_file) ? JSON.parse(File.read(bundle_file)) : JSON.parse("{}")
99
+ bundle[:bundle_data][:extensions] = load_bundle_extensions(bundle_path)
100
+ bundle[:measures] = []
101
+ Dir.glob(File.join(bundle_path, 'measures', '*')).each do |measure_dir|
102
+ load_measure(measure_dir).each do |measure|
103
+ bundle[:measures] << measure
104
+ end
105
+ end
106
+ bundle
107
+ end
108
+
109
+
110
+ # Load all of the extenson functions that will be available to map reduce functions from the bundle dir
111
+ # This will load from bundle_path/js and from ext directories in the individule measures directories
112
+ # like bundle_path/measures/0001/ext/
113
+ #@param [String] bundle_path the path to the bundle directory
114
+ def self.load_bundle_extensions(bundle_path)
115
+ extensions = []
116
+ Dir.glob(File.join(bundle_path, 'js', '*.js')).each do |js_file|
117
+ raw_js = File.read(js_file)
118
+ extensions << raw_js
119
+ end
120
+ Dir.glob(File.join(bundle_path, 'measures', '*','ext', '*.js')).each do |js_file|
121
+ raw_js = File.read(js_file)
122
+ extensions << raw_js
123
+ end
124
+ extensions
125
+ end
126
+
127
+
128
+ def self.load_from_zip(archive)
129
+ unzip_path = "./tmp/#{Time.new.to_i}/"
130
+ FileUtils.mkdir_p(unzip_path)
131
+ all_measures = []
132
+ Zip::ZipFile.foreach(archive) do |zipfile|
133
+ fname = unzip_path+ zipfile.name
134
+ FileUtils.rm fname, :force=>true
135
+ zipfile.extract(fname)
136
+ end
137
+ load_bundle(unzip_path)
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,15 @@
1
+ module QME
2
+ module MongoHelpers
3
+ # Evaluates underscore.js in the "js" directory of this project on
4
+ # the Mongo database passed in. This will make the library available
5
+ # to subsiquent calls on the database. This is useful for queries with
6
+ # where clauses or MapReduce functions
7
+ #
8
+ # @param [Mongo::DB] db The database to evaluate the JavaScript on
9
+ def self.initialize_javascript_frameworks(db,bundle_collection = 'bundles')
10
+ underscore_js = File.read(File.join(File.dirname(__FILE__), '..', '..', 'js', 'underscore-min.js'))
11
+ db.eval(underscore_js)
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,95 @@
1
+ require 'erb'
2
+
3
+ module QME
4
+ module Randomizer
5
+
6
+ # Provides functionality for randomizing patient records based on erb templates
7
+ class Patient
8
+
9
+ # Utility class used to supply a binding to Erb
10
+ class Context
11
+ # Create a new context
12
+ def initialize()
13
+ @genders = ['M', 'F']
14
+ # 300 most popular forenames according to US census 1990
15
+ @forenames = {
16
+ 'M' => %w{James John Robert Michael William David Richard Charles Joseph Thomas Christopher Daniel Paul Mark Donald George Kenneth Steven Edward Brian Ronald Anthony Kevin Jason Matthew Gary Timothy Jose Larry Jeffrey Frank Scott Eric Stephen Andrew Raymond Gregory Joshua Jerry Dennis Walter Patrick Peter Harold Douglas Henry Carl Arthur Ryan Roger Joe Juan Jack Albert Jonathan Justin Terry Gerald Keith Samuel Willie Ralph Lawrence Nicholas Roy Benjamin Bruce Brandon Adam Harry Fred Wayne Billy Steve Louis Jeremy Aaron Randy Howard Eugene Carlos Russell Bobby Victor Martin Ernest Phillip Todd Jesse Craig Alan Shawn Clarence Sean Philip Chris Johnny Earl Jimmy Antonio Danny Bryan Tony Luis Mike Stanley Leonard Nathan Dale Manuel Rodney Curtis Norman Allen Marvin Vincent Glenn Jeffery Travis Jeff Chad Jacob Lee Melvin Alfred Kyle Francis Bradley Jesus Herbert Frederick Ray Joel Edwin Don Eddie Ricky Troy Randall Barry Alexander Bernard Mario Leroy Francisco Marcus Micheal Theodore Clifford Miguel Oscar Jay Jim Tom Calvin Alex Jon Ronnie Bill Lloyd Tommy Leon Derek Warren Darrell Jerome Floyd Leo Alvin Tim Wesley Gordon Dean Greg Jorge Dustin Pedro Derrick Dan Lewis Zachary Corey Herman Maurice Vernon Roberto Clyde Glen Hector Shane Ricardo Sam Rick Lester Brent Ramon Charlie Tyler Gilbert Gene Marc Reginald Ruben Brett Angel Nathaniel Rafael Leslie Edgar Milton Raul Ben Chester Cecil Duane Franklin Andre Elmer Brad Gabriel Ron Mitchell Roland Arnold Harvey Jared Adrian Karl Cory Claude Erik Darryl Jamie Neil Jessie Christian Javier Fernando Clinton Ted Mathew Tyrone Darren Lonnie Lance Cody Julio Kelly Kurt Allan Nelson Guy Clayton Hugh Max Dwayne Dwight Armando Felix Jimmie Everett Jordan Ian Wallace Ken Bob Jaime Casey Alfredo Alberto Dave Ivan Johnnie Sidney Byron Julian Isaac Morris Clifton Willard Daryl Ross Virgil Andy Marshall Salvador Perry Kirk Sergio Marion Tracy Seth Kent Terrance Rene Eduardo Terrence Enrique Freddie Wade},
17
+ 'F' => %w{Mary Patricia Linda Barbara Elizabeth Jennifer Maria Susan Margaret Dorothy Lisa Nancy Karen Betty Helen Sandra Donna Carol Ruth Sharon Michelle Laura Sarah Kimberly Deborah Jessica Shirley Cynthia Angela Melissa Brenda Amy Anna Rebecca Virginia Kathleen Pamela Martha Debra Amanda Stephanie Carolyn Christine Marie Janet Catherine Frances Ann Joyce Diane Alice Julie Heather Teresa Doris Gloria Evelyn Jean Cheryl Mildred Katherine Joan Ashley Judith Rose Janice Kelly Nicole Judy Christina Kathy Theresa Beverly Denise Tammy Irene Jane Lori Rachel Marilyn Andrea Kathryn Louise Sara Anne Jacqueline Wanda Bonnie Julia Ruby Lois Tina Phyllis Norma Paula Diana Annie Lillian Emily Robin Peggy Crystal Gladys Rita Dawn Connie Florence Tracy Edna Tiffany Carmen Rosa Cindy Grace Wendy Victoria Edith Kim Sherry Sylvia Josephine Thelma Shannon Sheila Ethel Ellen Elaine Marjorie Carrie Charlotte Monica Esther Pauline Emma Juanita Anita Rhonda Hazel Amber Eva Debbie April Leslie Clara Lucille Jamie Joanne Eleanor Valerie Danielle Megan Alicia Suzanne Michele Gail Bertha Darlene Veronica Jill Erin Geraldine Lauren Cathy Joann Lorraine Lynn Sally Regina Erica Beatrice Dolores Bernice Audrey Yvonne Annette June Samantha Marion Dana Stacy Ana Renee Ida Vivian Roberta Holly Brittany Melanie Loretta Yolanda Jeanette Laurie Katie Kristen Vanessa Alma Sue Elsie Beth Jeanne Vicki Carla Tara Rosemary Eileen Terri Gertrude Lucy Tonya Ella Stacey Wilma Gina Kristin Jessie Natalie Agnes Vera Willie Charlene Bessie Delores Melinda Pearl Arlene Maureen Colleen Allison Tamara Joy Georgia Constance Lillie Claudia Jackie Marcia Tanya Nellie Minnie Marlene Heidi Glenda Lydia Viola Courtney Marian Stella Caroline Dora Jo Vickie Mattie Terry Maxine Irma Mabel Marsha Myrtle Lena Christy Deanna Patsy Hilda Gwendolyn Jennie Nora Margie Nina Cassandra Leah Penny Kay Priscilla Naomi Carole Brandy Olga Billie Dianne Tracey Leona Jenny Felicia Sonia Miriam Velma Becky Bobbie Violet Kristina Toni Misty Mae Shelly Daisy Ramona Sherri Erika Katrina Claire}
18
+ }
19
+ # 500 most popular surnames according to US census 1990
20
+ @surnames = %w{Smith Johnson Williams Jones Brown Davis Miller Wilson Moore Taylor Anderson Thomas Jackson White Harris Martin Thompson Garcia Martinez Robinson Clark Rodriguez Lewis Lee Walker Hall Allen Young Hernandez King Wright Lopez Hill Scott Green Adams Baker Gonzalez Nelson Carter Mitchell Perez Roberts Turner Phillips Campbell Parker Evans Edwards Collins Stewart Sanchez Morris Rogers Reed Cook Morgan Bell Murphy Bailey Rivera Cooper Richardson Cox Howard Ward Torres Peterson Gray Ramirez James Watson Brooks Kelly Sanders Price Bennett Wood Barnes Ross Henderson Coleman Jenkins Perry Powell Long Patterson Hughes Flores Washington Butler Simmons Foster Gonzales Bryant Alexander Russell Griffin Diaz Hayes Myers Ford Hamilton Graham Sullivan Wallace Woods Cole West Jordan Owens Reynolds Fisher Ellis Harrison Gibson Mcdonald Cruz Marshall Ortiz Gomez Murray Freeman Wells Webb Simpson Stevens Tucker Porter Hunter Hicks Crawford Henry Boyd Mason Morales Kennedy Warren Dixon Ramos Reyes Burns Gordon Shaw Holmes Rice Robertson Hunt Black Daniels Palmer Mills Nichols Grant Knight Ferguson Rose Stone Hawkins Dunn Perkins Hudson Spencer Gardner Stephens Payne Pierce Berry Matthews Arnold Wagner Willis Ray Watkins Olson Carroll Duncan Snyder Hart Cunningham Bradley Lane Andrews Ruiz Harper Fox Riley Armstrong Carpenter Weaver Greene Lawrence Elliott Chavez Sims Austin Peters Kelley Franklin Lawson Fields Gutierrez Ryan Schmidt Carr Vasquez Castillo Wheeler Chapman Oliver Montgomery Richards Williamson Johnston Banks Meyer Bishop Mccoy Howell Alvarez Morrison Hansen Fernandez Garza Harvey Little Burton Stanley Nguyen George Jacobs Reid Kim Fuller Lynch Dean Gilbert Garrett Romero Welch Larson Frazier Burke Hanson Day Mendoza Moreno Bowman Medina Fowler Brewer Hoffman Carlson Silva Pearson Holland Douglas Fleming Jensen Vargas Byrd Davidson Hopkins May Terry Herrera Wade Soto Walters Curtis Neal Caldwell Lowe Jennings Barnett Graves Jimenez Horton Shelton Barrett Obrien Castro Sutton Gregory Mckinney Lucas Miles Craig Rodriquez Chambers Holt Lambert Fletcher Watts Bates Hale Rhodes Pena Beck Newman Haynes Mcdaniel Mendez Bush Vaughn Parks Dawson Santiago Norris Hardy Love Steele Curry Powers Schultz Barker Guzman Page Munoz Ball Keller Chandler Weber Leonard Walsh Lyons Ramsey Wolfe Schneider Mullins Benson Sharp Bowen Daniel Barber Cummings Hines Baldwin Griffith Valdez Hubbard Salazar Reeves Warner Stevenson Burgess Santos Tate Cross Garner Mann Mack Moss Thornton Dennis Mcgee Farmer Delgado Aguilar Vega Glover Manning Cohen Harmon Rodgers Robbins Newton Todd Blair Higgins Ingram Reese Cannon Strickland Townsend Potter Goodwin Walton Rowe Hampton Ortega Patton Swanson Joseph Francis Goodman Maldonado Yates Becker Erickson Hodges Rios Conner Adkins Webster Norman Malone Hammond Flowers Cobb Moody Quinn Blake Maxwell Pope Floyd Osborne Paul Mccarthy Guerrero Lindsey Estrada Sandoval Gibbs Tyler Gross Fitzgerald Stokes Doyle Sherman Saunders Wise Colon Gill Alvarado Greer Padilla Simon Waters Nunez Ballard Schwartz Mcbride Houston Christensen Klein Pratt Briggs Parsons Mclaughlin Zimmerman French Buchanan Moran Copeland Roy Pittman Brady Mccormick Holloway Brock Poole Frank Logan Owen Bass Marsh Drake Wong Jefferson Park Morton Abbott Sparks Patrick Norton Huff Clayton Massey Lloyd Figueroa Carson Bowers Roberson Barton Tran Lamb Harrington Casey Boone Cortez Clarke Mathis Singleton Wilkins Cain Bryan Underwood Hogan Mckenzie Collier Luna Phelps Mcguire Allison Bridges Wilkerson Nash Summers Atkins}
21
+ end
22
+
23
+ # Pick a gender at random
24
+ # @return 'M' or 'F'
25
+ def gender
26
+ @genders[rand(@genders.length)]
27
+ end
28
+
29
+ # Pick a forename at random appropriate for the supplied gender
30
+ # @param [String] gender the gender 'M' or 'F'
31
+ # @return a suitable forename
32
+ def forename(gender)
33
+ @forenames[gender][rand(@forenames[gender].length)]
34
+ end
35
+
36
+ # Pick a surname at random
37
+ # @return a surname
38
+ def surname
39
+ @surnames[rand(@surnames.length)]
40
+ end
41
+
42
+ # Return a set of randomly selected numbers between two bounds
43
+ # @param [int] min the lower inclusive bound
44
+ # @param [int] max the upper inclusive bound
45
+ # @param [int] least the minimum count to return
46
+ # @param [int] most the maximum count to return
47
+ # @return [String] a JSON format array of numbers
48
+ def n_between(min, max, least=1, most=1)
49
+ count = least+rand(1+most-least).to_i
50
+ result = []
51
+ count.times do
52
+ result << between(min, max)
53
+ end
54
+ result.to_json
55
+ end
56
+
57
+ # Return a randomly selected number between two bounds
58
+ # @param [int] min the lower inclusive bound
59
+ # @param [int] max the upper inclusive bound
60
+ # @return [int] a JSON format array of numbers
61
+ def between(min, max)
62
+ span = max.to_i - min.to_i + 1
63
+ min.to_i+rand(span)
64
+ end
65
+
66
+ # Pick true or false according to the supplied probability
67
+ # @param [int] probability the probability of getting true as a percentage
68
+ # @return [boolean] true or false
69
+ def percent(probability)
70
+ return rand(100)<probability
71
+ end
72
+
73
+ # Get a binding that for the current instance
74
+ # @return [Binding]
75
+ def get_binding
76
+ binding
77
+ end
78
+ end
79
+
80
+ # Create a new instance with the supplied template
81
+ # @param [String] patient the patient template
82
+ def initialize(patient)
83
+ @template = ERB.new(patient)
84
+ end
85
+
86
+ # Get a randomized record based on the stored template
87
+ # @return [String] a randomized patient
88
+ def get
89
+ context = Context.new()
90
+ @template.result(context.get_binding)
91
+ end
92
+
93
+ end
94
+ end
95
+ end