kevintyll-ssn_validator 0.1.2 → 1.0.0

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.
data/History.txt CHANGED
@@ -3,8 +3,16 @@
3
3
  * 1 major enhancement:
4
4
  * Initial release
5
5
 
6
- == 0.1.2 2009-15-14
6
+ == 0.1.2 2009-04-15
7
7
 
8
8
  * 2 minor enhancements:
9
9
  * Added rdoc files
10
10
  * Updated documentation
11
+
12
+ == 1.0.0 2009-04-22
13
+
14
+ * 1 major enhancement:
15
+ * Added the death master file to determine if the ssn belongs to the deceased.
16
+
17
+ * 1 minor enhancement:
18
+ * Fixed bug where an error was thrown if the ssn area has not been assigned yet.
data/Manifest.txt CHANGED
@@ -5,8 +5,14 @@ README.rdoc
5
5
  Rakefile
6
6
  generators/ssn_validator_migration/templates/migration.rb
7
7
  generators/ssn_validator_migration/ssn_validator_migration_generator.rb
8
+ generators/death_master_file_migration/templates/migration.rb
9
+ generators/death_master_file_migration/death_master_file_migration_generator.rb
10
+ lib/ssn_validator/ntis.rb
8
11
  lib/ssn_validator/models/ssn_high_group_code.rb
12
+ lib/ssn_validator/models/ssn_high_group_code_loader.rb
9
13
  lib/ssn_validator/models/ssn_validator.rb
14
+ lib/ssn_validator/models/death_master_file.rb
15
+ lib/ssn_validator/models/death_master_file_loader.rb
10
16
  lib/ssn_validator.rb
11
17
  lib/tasks/ssn_validator.rake
12
18
  script/console
@@ -14,4 +20,8 @@ script/destroy
14
20
  script/generate
15
21
  test/test_helper.rb
16
22
  test/test_ssn_validator.rb
17
- test/test_ssn_high_group_code.rb
23
+ test/test_ssn_high_group_code_loader.rb
24
+ test/test_death_master_file_loader.rb
25
+ test/mocks/test/death_master_file_loader.rb
26
+ test/files/test_dmf_initial_load.txt
27
+ test/files/test_dmf_update_load.txt
data/PostInstall.txt CHANGED
@@ -1,9 +1,42 @@
1
1
 
2
2
  For more information on ssn_validator, see http://kevintyll.github.com/ssn_validator/
3
3
 
4
- * To create the necessary db migration, from the command line, run: script/generate ssn_validator_migration
5
- * Require the gem in your environment.rb file in the Rails::Initializer block: config.gem 'ssn_validator'
6
- * To load your table with the current SSN data, from the command line, run: rake ssn_validator:update_data
7
- * The SSN data is updated monthly, so you'll want to run this rake task monthly to keep your validations accurate.
4
+ * To create the necessary db migration, from the command line, run:
5
+ script/generate ssn_validator_migration
6
+ * Require the gem in your environment.rb file in the Rails::Initializer block:
7
+ config.gem 'kevintyll-ssn_validator', :lib => 'ssn_validator'
8
+ * To load your table with the current SSN data, from the command line, run:
9
+ rake ssn_validator:update_data
10
+
11
+ * The SSN data is updated monthly, so you'll want to run this rake task monthly to keep your validations accurate.
12
+
13
+ * If you've purchased the death master file data, create the death_master_files migration:
14
+ script/generate death_master_file_migration
15
+ * To load the dmf files you receive from ntis:
16
+ rake ssn_validator:death_master_file:load_file PATH='path/to/file' AS_OF='2009-03-01'
17
+
18
+ * You'll need to pass in the full path to where the file is on disk. You'll also need
19
+ to pass in the date this file's data are as of in the format yyyy-mm-dd.
20
+
21
+ * This task must be used to load the initial file you receive from ntis on CD. It can optionally be used
22
+ to load the monthly update files you download from the website. If you manually download the update files,
23
+ you do not need to add your user name and password to the environment.rb file. For a more automated approach
24
+ to loading the update files, add your user name and password to the environment.rb file and use the 2nd rake task.
25
+
26
+ * To load the monthly updates from the ntis website:
27
+ * Add your user_name and password to the environment.rb file
28
+
29
+ SsnValidator::Ntis.user_name = 'REPLACE WITH YOUR dmf.ntis.gov USER NAME'
30
+ SsnValidator::Ntis.password = 'REPLACE WITH YOUR dmf.ntis.gov PASSWORD'
31
+
32
+ * Run the rake task:
33
+
34
+ rake ssn_validator:death_master_file:update_data
35
+
36
+ * This rake task will determine the most recent file that has been loaded, and loads all subsequent files in order
37
+ from the dmf.ntis.gov website.
38
+
39
+ * The death master file data is updated monthly, so you'll want to run this rake task monthly to keep your validations accurate.
40
+
8
41
 
9
42
 
data/README.rdoc CHANGED
@@ -13,7 +13,7 @@ has ever been issued or not.
13
13
 
14
14
  ssn_validator started as a need for the company I work for, Clarity Services Inc. Incredibly, we
15
15
  couldn't find an existing gem or service that provided what we needed. Since we were going to have
16
- to role our own solution, we decided to create a gem out of it and share it with the community. Much
16
+ to roll our own solution, we decided to create a gem out of it and share it with the community. Much
17
17
  thanks goes to the management at Clarity Services Inc. for allowing this code to be open sourced.
18
18
 
19
19
 
@@ -21,13 +21,13 @@ thanks goes to the management at Clarity Services Inc. for allowing this code to
21
21
 
22
22
  * What it can do:
23
23
  Validates the likelyhood that an SSN has been issued to someone.
24
+ Checks the Death Master File if the SSN belongs to a dead person. This will require you to purchase the dmf data from https://dmf.ntis.gov
24
25
 
25
26
  * What it cannot do:
26
27
  Validate that an SSN actually belongs to a particular person.
27
28
 
28
29
  * What it's planned to do:
29
30
  Determine when an SSN was issued...if i can find the historical data. This can be used to further validate an SSN by comparing it to a Date of Birth.
30
- Check the Death Master File if the SSN belongs to a dead person. This will require you to purchase the dmf data from https://dmf.ntis.gov
31
31
 
32
32
  == SYNOPSIS:
33
33
 
@@ -36,6 +36,14 @@ thanks goes to the management at Clarity Services Inc. for allowing this code to
36
36
 
37
37
  * Then check if it's valid
38
38
  ssn.valid?
39
+ ssn.death_master_file_hit?
40
+
41
+ ssn.valid? only checks to see if the number itself is valid. Check the death_master_file_hit? method
42
+ as well to verify the ssn does not belong the the deceased.
43
+
44
+ The death_master_file_hit? method will only return true of false if you have purchased the dmf data and
45
+ loaded it into your database. There is a generator and rake task to facilitate this, but you'll have
46
+ to purchase the data yourself at https://dmf.ntis.gov.
39
47
 
40
48
  * You can check the errors array to see why it's not valid.
41
49
  ssn.errors
@@ -51,10 +59,39 @@ thanks goes to the management at Clarity Services Inc. for allowing this code to
51
59
  * To create the necessary db migration, from the command line, run:
52
60
  script/generate ssn_validator_migration
53
61
  * Require the gem in your environment.rb file in the Rails::Initializer block:
54
- config.gem 'ssn_validator'
62
+ config.gem 'kevintyll-ssn_validator', :lib => 'ssn_validator'
55
63
  * To load your table with the current SSN data, from the command line, run:
56
64
  rake ssn_validator:update_data
57
- * The SSN data is updated monthly, so you'll want to run this rake task monthly to keep your validations accurate.
65
+
66
+ * The SSN data is updated monthly, so you'll want to run this rake task monthly to keep your validations accurate.
67
+
68
+ * If you've purchased the death master file data, create the death_master_files migration:
69
+ script/generate death_master_file_migration
70
+ * To load the dmf files you receive from ntis:
71
+ rake ssn_validator:death_master_file:load_file PATH='path/to/file' AS_OF='2009-03-01'
72
+
73
+ * You'll need to pass in the full path to where the file is on disk. You'll also need
74
+ to pass in the date this file's data are as of in the format yyyy-mm-dd.
75
+
76
+ * This task must be used to load the initial file you receive from ntis on CD. It can optionally be used
77
+ to load the monthly update files you download from the website. If you manually download the update files,
78
+ you do not need to add your user name and password to the environment.rb file. For a more automated approach
79
+ to loading the update files, add your user name and password to the environment.rb file and use the 2nd rake task.
80
+
81
+ * To load the monthly updates from the ntis website:
82
+ * Add your user_name and password to the environment.rb file
83
+
84
+ SsnValidator::Ntis.user_name = 'REPLACE WITH YOUR dmf.ntis.gov USER NAME'
85
+ SsnValidator::Ntis.password = 'REPLACE WITH YOUR dmf.ntis.gov PASSWORD'
86
+
87
+ * Run the rake task:
88
+
89
+ rake ssn_validator:death_master_file:update_data
90
+
91
+ * This rake task will determine the most recent file that has been loaded, and loads all subsequent files in order
92
+ from the dmf.ntis.gov website.
93
+
94
+ * The death master file data is updated monthly, so you'll want to run this rake task monthly to keep your validations accurate.
58
95
 
59
96
  == LICENSE:
60
97
 
data/Rakefile CHANGED
@@ -13,8 +13,8 @@ $hoe = Hoe.new('ssn_validator', SsnValidator::VERSION) do |p|
13
13
  p.extra_dev_deps = [
14
14
  ['newgem', ">= #{::Newgem::VERSION}"]
15
15
  ]
16
-
17
- p.clean_globs |= %w[**/.DS_Store tmp *.log]
16
+
17
+ p.clean_globs |= %w[**/.DS_Store tmp *.log nbproject pkg]
18
18
  path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
19
19
  p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
20
20
  p.rsync_args = '-av --delete --ignore-errors'
@@ -0,0 +1,12 @@
1
+ class DeathMasterFileMigrationGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ #m.directory File.join('db')
5
+ m.migration_template 'migration.rb', 'db/migrate'
6
+ end
7
+ end
8
+
9
+ def file_name
10
+ "create_death_master_file_table"
11
+ end
12
+ end
@@ -0,0 +1,26 @@
1
+ class CreateDeathMasterFileTable < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ create_table :death_master_files do |t|
5
+ t.string :social_security_number
6
+ t.string :last_name
7
+ t.string :name_suffix
8
+ t.string :first_name
9
+ t.string :middle_name
10
+ t.string :verify_proof_code
11
+ t.date :date_of_death
12
+ t.date :date_of_birth
13
+ t.string :state_of_residence
14
+ t.string :last_known_zip_residence
15
+ t.string :last_known_zip_payment
16
+ t.time_stamps
17
+ t.date :as_of
18
+ end
19
+ add_index :death_master_files, :social_security_number, :unique => true
20
+ add_index :death_master_files, :as_of
21
+ end
22
+
23
+ def self.down
24
+ drop_table :death_master_files
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ require 'activerecord'
2
+
3
+ class DeathMasterFile < ActiveRecord::Base
4
+
5
+ end
@@ -0,0 +1,235 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'activerecord'
4
+ require 'active_record/connection_adapters/mysql_adapter'
5
+ require 'ssn_validator/ntis'
6
+
7
+ class DeathMasterFileLoader
8
+
9
+ # path_or_url is the full path to the file to load on disk, or the url of an update file.
10
+ # as_of is a string in the formatt YYYY-MM-DD for which the file data is accurate.
11
+ def initialize(path_or_url,file_as_of)
12
+ @file_path_or_url = path_or_url
13
+ @file_as_of = file_as_of
14
+ valid?
15
+ end
16
+
17
+ def valid?
18
+ raise(ArgumentError, "path_or_url not specified") unless @file_path_or_url
19
+ raise(ArgumentError, "as_of not specified") unless @file_as_of
20
+ max_as_of = DeathMasterFile.maximum(:as_of)
21
+ raise(ArgumentError, "A more recent file has already been processed. DB as_of date #{max_as_of}") if max_as_of && (max_as_of >= @file_as_of.to_date)
22
+
23
+ if File.exists?(@file_path_or_url)
24
+ @download_file = File.open(@file_path_or_url)
25
+ elsif URI.parse(@file_path_or_url).kind_of?(URI::HTTP)
26
+ @download_file = get_file_from_web
27
+ else
28
+ raise(Errno::ENOENT, @file_path_or_url)
29
+ end
30
+ end
31
+
32
+ def load_file
33
+
34
+ if DeathMasterFile.connection.kind_of?(ActiveRecord::ConnectionAdapters::MysqlAdapter)
35
+ puts "Converting file to csv format for Mysql import. This could take several minutes."
36
+
37
+ csv_file = convert_file_to_csv
38
+
39
+ bulk_mysql_update(csv_file)
40
+ else
41
+ active_record_file_load
42
+ end
43
+
44
+ end
45
+
46
+ def get_file_from_web
47
+ uri = URI.parse(@file_path_or_url)
48
+
49
+ request = Net::HTTP::Get.new(uri.request_uri)
50
+ request.basic_auth(SsnValidator::Ntis.user_name,SsnValidator::Ntis.password)
51
+
52
+ http = Net::HTTP.new(uri.host, uri.port)
53
+ http.use_ssl = (uri.port == 443)
54
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
55
+
56
+ response = http.request(request)
57
+
58
+ raise(ArgumentError, "Invalid URL: #{@file_path_or_url}") if response.kind_of?(Net::HTTPNotFound)
59
+ raise(ArgumentError, "Authorization Required: Invalid username or password. Set the variables SsnValidator::Ntis.user_name and SsnValidator::Ntis.password in your environment.rb file.") if response.kind_of?(Net::HTTPUnauthorized)
60
+
61
+ return response.body
62
+ end
63
+
64
+ #Loads all the update files from dmf.ntis.gov.
65
+ #It starts with the last file loaded, and loads each
66
+ #missing file in sequence up to the current file.
67
+ def self.load_update_files_from_web
68
+ max_as_of = DeathMasterFile.maximum(:as_of)
69
+ run_file_date = max_as_of.beginning_of_month.next_month
70
+ last_file_date = Date.today.beginning_of_month
71
+ while run_file_date <= last_file_date
72
+ url = "https://dmf.ntis.gov/dmldata/monthly/MA#{run_file_date.strftime("%y%m%d")}"
73
+ puts "Loading file #{url}"
74
+ DeathMasterFileLoader.new(url,run_file_date.strftime("%Y-%m-%d")).load_file
75
+ run_file_date += 1.month
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ # Processes 28 million rows in 23 minutes. Input file 2.6GB output: 2.9GB.
82
+ # Used to convert a packed fixed-length file into csv for mysql import.
83
+ def convert_file_to_csv
84
+ #packed_file = File.open(file_path)
85
+ csv_file = Tempfile.new("dmf") # create temp file for converted csv formmat.
86
+
87
+
88
+ start = Time.now
89
+ timenow = start.to_s(:db)
90
+
91
+ @delete_ssns = []
92
+
93
+ @download_file.each_with_index do |line,i|
94
+ action = record_action(line)
95
+ attributes_hash = text_to_hash(line)
96
+ if action == 'D'
97
+ #keep track of all the records to delete. We'll delete at the end all at once.
98
+ @delete_ssns << attributes_hash[:social_security_number]
99
+ else
100
+ # empty field for id to be generated by mysql.
101
+ newline = "''," +
102
+ # social_security_number
103
+ "'#{attributes_hash[:social_security_number]}'," +
104
+ # last_name
105
+ "'#{attributes_hash[:last_name]}'," +
106
+ # name_suffix
107
+ "'#{attributes_hash[:name_suffix]}'," +
108
+ # first_name
109
+ "'#{attributes_hash[:first_name]}'," +
110
+ # middle_name
111
+ "'#{attributes_hash[:middle_name]}'," +
112
+ # verify_proof_code
113
+ "'#{attributes_hash[:verify_proof_code]}'," +
114
+ # date_of_death - need YYYY-MM-DD.
115
+ "'#{attributes_hash[:date_of_death]}'," +
116
+ # date_of_birth - need YYYY-MM-DD.
117
+ "'#{attributes_hash[:date_of_birth]}'," +
118
+ # state_of_residence - must be code between 01 and 65 or else nil.
119
+ "'#{attributes_hash[:state_of_residence]}'," +
120
+ # last_known_zip_residence
121
+ "'#{attributes_hash[:last_known_zip_residence]}'," +
122
+ # last_known_zip_payment
123
+ "'#{attributes_hash[:last_known_zip_payment]}'," +
124
+ # created_at
125
+ "'#{timenow}'," +
126
+ # updated_at
127
+ "'#{timenow}'," +
128
+ # as_of
129
+ "'#{attributes_hash[:as_of]}'" +"\n"
130
+
131
+ csv_file.puts newline
132
+ puts "#{i} records processed." if (i % 25000 == 0) && (i > 0)
133
+ end
134
+ end
135
+ puts "File conversion ran for #{(Time.now - start) / 60} minutes."
136
+ return csv_file
137
+ end
138
+
139
+ #Uses active record to load the data.
140
+ #The benefit is it will work on any database.
141
+ #The downside is it's really slow.
142
+ def active_record_file_load
143
+ puts 'Importing file into database. This could take many minutes.'
144
+
145
+ @download_file.each_with_index do |line,i|
146
+ action = record_action(line)
147
+ attributes_hash = text_to_hash(line)
148
+ if action == 'D'
149
+ DeathMasterFile.destroy_all(['social_security_number = ?', attributes_hash[:social_security_number]])
150
+ else
151
+
152
+ # empty field for id to be generated by mysql.
153
+ # record_hash = {
154
+ # :as_of => @file_as_of.to_date.to_s(:db),
155
+ # :social_security_number => parse_record(line,:social_security_number),
156
+ # :last_name => parse_record(line,:last_name),
157
+ # :name_suffix => parse_record(line,:name_suffix),
158
+ # :first_name => parse_record(line,:first_name),
159
+ # :middle_name => parse_record(line,:middle_name),
160
+ # :verify_proof_code => parse_record(line,:verify_proof_code),
161
+ # :date_of_death => parse_record(line,:date_of_death),
162
+ # :date_of_birth => parse_record(line,:date_of_birth),
163
+ # # - must be code between 01 and 65 or else nil.
164
+ # :state_of_residence=> parse_record(line,:state_of_residence=),
165
+ # :last_known_zip_residence => parse_record(line,:last_known_zip_residence),
166
+ # :last_known_zip_payment => parse_record(line,:last_known_zip_payment)
167
+ # }
168
+
169
+ case action
170
+ when '',nil,' '
171
+ #the initial file leaves this field blank
172
+ DeathMasterFile.create(attributes_hash)
173
+ else
174
+ dmf = DeathMasterFile.find_by_social_security_number(attributes_hash[:social_security_number])
175
+ if dmf
176
+ #a record already exists, update this record
177
+ dmf.update_attributes(attributes_hash)
178
+ else
179
+ #create a new record
180
+ DeathMasterFile.create(attributes_hash)
181
+ end
182
+ end
183
+ end
184
+
185
+ puts "#{i} records processed." if (i % 2500 == 0) && (i > 0)
186
+
187
+ end
188
+
189
+ puts "Import complete."
190
+ end
191
+
192
+ # For mysql, use:
193
+ # LOAD DATA LOCAL INFILE 'ssdm1.csv' INTO TABLE death_master_files FIELDS TERMINATED BY ',' ENCLOSED BY "'" LINES TERMINATED BY '\n';
194
+ # This is a much faster way of loading large amounts of data into mysql. For information on the LOAD DATA command
195
+ # see http://dev.mysql.com/doc/refman/5.1/en/load-data.html
196
+ def bulk_mysql_update(csv_file)
197
+ puts "Importing into Mysql..."
198
+
199
+ #delete all the 'D' records
200
+ DeathMasterFile.destroy_all(:social_security_number => @delete_ssns)
201
+
202
+ #This will insert new records, and replace records with existing ssns.
203
+ #This only works because there is a unique index on social_security_number.
204
+ mysql_command = <<TEXT
205
+ LOAD DATA LOCAL INFILE '#{csv_file.path}' REPLACE INTO TABLE death_master_files FIELDS TERMINATED BY ',' ENCLOSED BY "'" LINES TERMINATED BY '\n';
206
+ TEXT
207
+
208
+ DeathMasterFile.connection.execute(mysql_command)
209
+ puts "Mysql import complete."
210
+
211
+ end
212
+
213
+ def record_action(line)
214
+ line[0,1].to_s.strip
215
+ end
216
+
217
+ def text_to_hash(line)
218
+
219
+ {:as_of => @file_as_of.to_date.to_s(:db),
220
+ :social_security_number => line[1,9].to_s.strip,
221
+ :last_name => line[10,20].to_s.strip,
222
+ :name_suffix => line[30,4].to_s.strip,
223
+ :first_name => line[34,15].to_s.strip,
224
+ :middle_name => line[49,15].to_s.strip,
225
+ :verify_proof_code => line[64,1].to_s.strip,
226
+ :date_of_death => (Date.strptime(line[65,8].to_s.strip,'%m%d%Y') rescue nil),
227
+ :date_of_birth => (Date.strptime(line[73,8].to_s.strip,'%m%d%Y') rescue nil),
228
+ # - must be code between 01 and 65 or else nil.
229
+ :state_of_residence => (line[81,2].to_s.strip.between?('01', '65') ? line[81,2].to_s.strip : nil),
230
+ :last_known_zip_residence => line[83,5].to_s.strip,
231
+ :last_known_zip_payment => line[88,5].to_s.strip}
232
+ rescue Exception => e
233
+ puts '@@@@@@@@@ Error = ' + e.message + ': ' + line.inspect
234
+ end
235
+ end
@@ -1,81 +1,5 @@
1
1
  require 'activerecord'
2
- require 'net/http'
3
- class SsnHighGroupCode < ActiveRecord::Base
4
-
5
- # def self.load_historical_high_group_codes_file
6
- # ['Jan','Feb','Mar','Apr','May','June','July','Aug','Sept','Oct','Nov','Dec'].each do |month|
7
- # (1..10).each do |day|
8
- # string_day = day.to_s
9
- # string_day.insert(0,'0') if day < 10
10
- # current_year = Date.today.year
11
- # #(1932..current_year).each do |year|
12
- # [2003].each do |year|
13
- # string_year = year.to_s.last(2)
14
- # ['','corrected'].each do |mod|
15
- # ['ssns','ssnvs'].each do |url_mod|
16
- # file_name = "HG#{month}#{string_day}#{string_year}#{mod}.txt"
17
- # text = Net::HTTP.get(URI.parse("http://www.socialsecurity.gov/employer/#{url_mod}/#{file_name}"))
18
- # puts file_name.inspect
19
- # puts '@@@@@@@@@ found file_name = ' + file_name.inspect unless text.include? 'File Not Found'
20
- # end
21
- # end
22
- #
23
- # end
24
- # end
25
- #
26
- # end
27
- # end
28
-
29
- #Loads the most recent file from http://www.socialsecurity.gov/employer/ssns/highgroup.txt
30
- def self.load_current_high_group_codes_file
31
- text = Net::HTTP.get(URI.parse('http://www.socialsecurity.gov/employer/ssns/highgroup.txt'))
32
- create_records(parse_text(text),extract_as_of_date(text))
33
- end
34
-
35
-
36
- private
37
2
 
38
- def self.already_loaded?(file_as_of_date)
39
- self.find_by_as_of(file_as_of_date)
40
- end
41
-
42
- def self.create_records(area_groups,file_as_of)
43
- if already_loaded?(file_as_of)
44
- "File as of #{file_as_of} has already been loaded."
45
- else
46
- area_groups.each do |area_group|
47
- self.create(area_group.merge!(:as_of => file_as_of.to_s(:db)))
48
- end
49
- "File as of #{file_as_of} loaded."
50
- end
51
- end
52
-
53
- #extract the date from the file in the format mm/dd/yy
54
- def self.extract_as_of_date(text)
55
- as_of_start_index = text =~ /\d\d\/\d\d\/\d\d/
56
- ::Date.new(*::Date._parse($&,true).values_at(:year, :mon, :mday)) unless as_of_start_index.nil?
57
- end
58
-
59
- #The formatting of the file is a little bit messy. Sometimes tabs are
60
- #used as delimiters and sometimes spaces are used as delimiters. Also, the asterisks indicating recent changes are not
61
- #necessary for our purposes
62
- #Returns an array of hashes.
63
- def self.parse_text(text)
64
- text.gsub!('*',' ')
65
- text.gsub!(/\t/, ' ')
66
- text_array = text.split(/\n/).compact
67
- area_groups = []
68
- text_array.each do |t|
69
- t.gsub!(/\s+/,' ')
70
- next if t =~ /[[:alpha:]]/ #skip over the header lines
3
+ class SsnHighGroupCode < ActiveRecord::Base
71
4
 
72
- if t =~ /\d\d\d \d\d/ #we want the lines with area group pairs
73
- t.gsub(/\d\d\d \d\d/) do |s|
74
- area_group = s.split(' ')
75
- area_groups << {:area => area_group.first, :group => area_group.last}
76
- end
77
- end
78
- end
79
- return area_groups
80
- end
81
5
  end
@@ -0,0 +1,80 @@
1
+
2
+ require 'net/http'
3
+ class SsnHighGroupCodeLoader
4
+ # def self.load_historical_high_group_codes_file
5
+ # ['Jan','Feb','Mar','Apr','May','June','July','Aug','Sept','Oct','Nov','Dec'].each do |month|
6
+ # (1..10).each do |day|
7
+ # string_day = day.to_s
8
+ # string_day.insert(0,'0') if day < 10
9
+ # current_year = Date.today.year
10
+ # #(1932..current_year).each do |year|
11
+ # [2003].each do |year|
12
+ # string_year = year.to_s.last(2)
13
+ # ['','corrected'].each do |mod|
14
+ # ['ssns','ssnvs'].each do |url_mod|
15
+ # file_name = "HG#{month}#{string_day}#{string_year}#{mod}.txt"
16
+ # text = Net::HTTP.get(URI.parse("http://www.socialsecurity.gov/employer/#{url_mod}/#{file_name}"))
17
+ # puts file_name.inspect
18
+ # puts '@@@@@@@@@ found file_name = ' + file_name.inspect unless text.include? 'File Not Found'
19
+ # end
20
+ # end
21
+ #
22
+ # end
23
+ # end
24
+ #
25
+ # end
26
+ # end
27
+
28
+ #Loads the most recent file from http://www.socialsecurity.gov/employer/ssns/highgroup.txt
29
+ def self.load_current_high_group_codes_file
30
+ text = Net::HTTP.get(URI.parse('http://www.socialsecurity.gov/employer/ssns/highgroup.txt'))
31
+ create_records(parse_text(text),extract_as_of_date(text))
32
+ end
33
+
34
+
35
+ private
36
+
37
+ def self.already_loaded?(file_as_of_date)
38
+ SsnHighGroupCode.find_by_as_of(file_as_of_date)
39
+ end
40
+
41
+ def self.create_records(area_groups,file_as_of)
42
+ if already_loaded?(file_as_of)
43
+ puts "File as of #{file_as_of} has already been loaded."
44
+ else
45
+ area_groups.each do |area_group|
46
+ SsnHighGroupCode.create(area_group.merge!(:as_of => file_as_of.to_s(:db)))
47
+ end
48
+ puts "File as of #{file_as_of} loaded."
49
+ end
50
+ end
51
+
52
+ #extract the date from the file in the format mm/dd/yy
53
+ def self.extract_as_of_date(text)
54
+ as_of_start_index = text =~ /\d\d\/\d\d\/\d\d/
55
+ Date.strptime($&,'%m/%d/%y') unless as_of_start_index.nil?
56
+ end
57
+
58
+ #The formatting of the file is a little bit messy. Sometimes tabs are
59
+ #used as delimiters and sometimes spaces are used as delimiters. Also, the asterisks indicating recent changes are not
60
+ #necessary for our purposes
61
+ #Returns an array of hashes.
62
+ def self.parse_text(text)
63
+ text.gsub!('*',' ')
64
+ text.gsub!(/\t/, ' ')
65
+ text_array = text.split(/\n/).compact
66
+ area_groups = []
67
+ text_array.each do |t|
68
+ t.gsub!(/\s+/,' ')
69
+ next if t =~ /[[:alpha:]]/ #skip over the header lines
70
+
71
+ if t =~ /\d\d\d \d\d/ #we want the lines with area group pairs
72
+ t.gsub(/\d\d\d \d\d/) do |s|
73
+ area_group = s.split(' ')
74
+ area_groups << {:area => area_group.first, :group => area_group.last}
75
+ end
76
+ end
77
+ end
78
+ return area_groups
79
+ end
80
+ end
@@ -1,7 +1,7 @@
1
1
  module SsnValidator
2
2
  class Ssn
3
3
 
4
- attr_reader :ssn,:area,:group,:serial_number
4
+ attr_reader :ssn,:area,:group,:serial_number,:as_of
5
5
  attr_reader :errors
6
6
 
7
7
 
@@ -42,6 +42,7 @@ module SsnValidator
42
42
  if @ssn_high_group_code.nil?
43
43
  @errors << "Area '#{@area}' has not been assigned."
44
44
  else
45
+ @as_of = @ssn_high_group_code.as_of
45
46
 
46
47
  define_group_ranks
47
48
 
@@ -60,6 +61,11 @@ module SsnValidator
60
61
  @errors.empty?
61
62
  end
62
63
 
64
+ #Determines if the passed in ssn belongs to the deceased.
65
+ def death_master_file_hit?
66
+ DeathMasterFile.find_by_social_security_number(@ssn)
67
+ end
68
+
63
69
 
64
70
  private
65
71
 
@@ -0,0 +1,9 @@
1
+ module SsnValidator
2
+ class Ntis
3
+ @@user_name = 'REPLACE WITH YOUR dmf.ntis.gov USER NAME'
4
+ cattr_accessor :user_name
5
+
6
+ @@password = 'REPLACE WITH YOUR dmf.ntis.gov PASSWORD'
7
+ cattr_accessor :password
8
+ end
9
+ end
data/lib/ssn_validator.rb CHANGED
@@ -2,12 +2,15 @@ $:.unshift(File.dirname(__FILE__)) unless
2
2
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
3
 
4
4
  require 'ssn_validator/models/ssn_high_group_code'
5
+ require 'ssn_validator/models/ssn_high_group_code_loader'
5
6
  require 'ssn_validator/models/ssn_validator'
7
+ require 'ssn_validator/models/death_master_file'
8
+ require 'ssn_validator/models/death_master_file_loader'
6
9
  require 'rake'
7
10
 
8
11
  # Load rake file
9
12
  import "#{File.dirname(__FILE__)}/tasks/ssn_validator.rake"
10
13
 
11
14
  module SsnValidator
12
- VERSION = '0.1.2'
15
+ VERSION = '1.0.0'
13
16
  end
@@ -2,7 +2,23 @@
2
2
  namespace :ssn_validator do
3
3
  desc "Loads the current file from http://www.socialsecurity.gov/employer/ssns/highgroup.txt if it hasn't already been loaded."
4
4
  task :update_data => :environment do
5
- puts SsnHighGroupCode.load_current_high_group_codes_file
5
+ SsnHighGroupCode.load_current_high_group_codes_file
6
6
  end
7
-
7
+
8
+ namespace :death_master_file do
9
+ desc "Loads a death master file. Specify the path of the file: PATH=path. Specify the date of the data: AS_OF=YYYY-MM-DD. Optimized for Mysql databases."
10
+ task :load_file => :environment do
11
+ if ENV["PATH"] && ENV["AS_OF"]
12
+ DeathMasterFileLoader.new(ENV["PATH"],ENV["AS_OF"]).load_file
13
+ else
14
+ puts "You must specify the PATH and AS_OF variables: rake ssn_validator:death_master_file:load_file PATH='path/to/file' AS_OF='2009-03-01'"
15
+ end
16
+ end
17
+
18
+ desc "Determines the most recent file that has been loaded, and loads all subsequent files in order from the dmf.ntis.gov website. Optimized for Mysql databases."
19
+ task :update_data => :environment do
20
+ DeathMasterFileLoader.load_update_files_from_web
21
+ end
22
+ end
23
+
8
24
  end
@@ -0,0 +1,5 @@
1
+ 772781978NARANJO ANTHONY GABRIEL P030320090130200910
2
+ 772783025MANCO ESPICHAN JUAN P030220091101191010
3
+ 772786197OWENS ISAIAH P0202200901102009FO
4
+ 772789442ASTACIO JUANJOSE TOMAS P021320090129200910
5
+ 772789569BROWN ALANNAH SYEISSIE NICOL P030720090125200910
@@ -0,0 +1,5 @@
1
+ D772781978NARANJO ANTHONY GABRIEL P030320090130200910
2
+ A772783025REPLACED JUAN P030220091101191010
3
+ A772786198NEW ISAIAH P0202200901102009FO
4
+ D772789442CHANGED JUANJOSE TOMAS P021320090129200910
5
+ C772789569CHANGED ALANNAH SYEISSIE NICOL P030720090125200910
@@ -0,0 +1,35 @@
1
+ require 'ssn_validator/models/death_master_file_loader'
2
+
3
+ class DeathMasterFileLoader
4
+
5
+ def get_file_from_web
6
+ case @file_path_or_url
7
+ when /MA\d\d\d\d\d\d/ #these are the valid urls I want to mock a response to.
8
+ first_upload = Date.today.beginning_of_month - 2.months #based on the test, we know we are loading the last 3 months
9
+ if @file_path_or_url =~ /MA#{first_upload.strftime("%y%m%d")}/
10
+ return ['A772783123UPDATED JUAN P030220091101191010']
11
+ elsif @file_path_or_url =~ /MA#{(first_upload + 1.month).strftime("%y%m%d")}/
12
+ return ['A772783456UPDATED JUAN P030220091101191010']
13
+ elsif @file_path_or_url =~ /MA#{(first_upload + 2.months).strftime("%y%m%d")}/
14
+ return ['A772783789UPDATED JUAN P030220091101191010']
15
+ end
16
+ else
17
+ uri = URI.parse(@file_path_or_url)
18
+
19
+ request = Net::HTTP::Get.new(uri.request_uri)
20
+ request.basic_auth(SsnValidator::Ntis.user_name,SsnValidator::Ntis.password)
21
+
22
+ http = Net::HTTP.new(uri.host, uri.port)
23
+ http.use_ssl = (uri.port == 443)
24
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
25
+
26
+ response = http.request(request)
27
+
28
+ raise(ArgumentError, "Invalid URL: #{@file_path_or_url}") if response.kind_of?(Net::HTTPNotFound)
29
+ raise(ArgumentError, "Authorization Required: Invalid username or password. Set the variables SsnValidator::Ntis.user_name and SsnValidator::Ntis.password in your environment.rb file.") if response.kind_of?(Net::HTTPUnauthorized)
30
+
31
+ return response.body
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,85 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestDeathMasterFileLoader < Test::Unit::TestCase
4
+
5
+ def setup
6
+ setup_death_master_file_table
7
+ end
8
+
9
+ def test_validations_for_load_file
10
+ exception = assert_raise ArgumentError do
11
+ DeathMasterFileLoader.new(nil,nil)
12
+ end
13
+ assert_equal "path_or_url not specified", exception.message
14
+
15
+ exception = assert_raise ArgumentError do
16
+ DeathMasterFileLoader.new('some/path.txt',nil)
17
+ end
18
+ assert_equal "as_of not specified", exception.message
19
+
20
+ exception = assert_raise Errno::ENOENT do
21
+ DeathMasterFileLoader.new('some/path.txt','2009-01-01')
22
+ end
23
+ assert_equal "No such file or directory - some/path.txt", exception.message
24
+
25
+ exception = assert_raise URI::InvalidURIError do
26
+ DeathMasterFileLoader.new('https://dmf.bad_url.gov/dmldata/monthly/bad_url','2009-01-01')
27
+ end
28
+ assert_equal "the scheme https does not accept registry part: dmf.bad_url.gov (or bad hostname?)", exception.message
29
+
30
+ exception = assert_raise ArgumentError do
31
+ DeathMasterFileLoader.new('https://dmf.ntis.gov/bad_url','2009-01-01')
32
+ end
33
+ assert_equal "Invalid URL: https://dmf.ntis.gov/bad_url", exception.message
34
+
35
+ exception = assert_raise ArgumentError do
36
+ DeathMasterFileLoader.new('https://dmf.ntis.gov/dmldata/monthly/bad_url','2009-01-01')
37
+ end
38
+ assert_equal "Authorization Required: Invalid username or password. Set the variables SsnValidator::Ntis.user_name and SsnValidator::Ntis.password in your environment.rb file.", exception.message
39
+ end
40
+
41
+ def test_should_load_table_from_file
42
+ assert_equal(0,DeathMasterFile.count)
43
+ DeathMasterFileLoader.new(File.dirname(__FILE__) + '/files/test_dmf_initial_load.txt','2009-01-01').load_file
44
+ assert DeathMasterFile.count == 5
45
+
46
+ #load update file
47
+ #update file adds 1 record, deletes 2 records, and changes 2 records
48
+ DeathMasterFileLoader.new(File.dirname(__FILE__) + '/files/test_dmf_update_load.txt','2009-02-01').load_file
49
+ assert DeathMasterFile.count == 4
50
+ assert 1, DeathMasterFile.count(:conditions => {:last_name => 'NEW'})
51
+ assert 2, DeathMasterFile.count(:conditions => {:last_name => 'CHANGED'})
52
+ end
53
+
54
+ def test_should_not_load_table_if_as_of_already_in_table_for_load_file
55
+ DeathMasterFileLoader.new(File.dirname(__FILE__) + '/files/test_dmf_initial_load.txt','2009-01-01').load_file
56
+ assert DeathMasterFile.count > 0
57
+ record_count = DeathMasterFile.count
58
+
59
+ exception = assert_raise ArgumentError do
60
+ DeathMasterFileLoader.new(File.dirname(__FILE__) + '/files/test_dmf_update_load.txt','2008-01-01').load_file
61
+ end
62
+ assert_equal "A more recent file has already been processed. DB as_of date 2009-01-01", exception.message
63
+
64
+ assert_equal(record_count, DeathMasterFile.count)
65
+ end
66
+
67
+ def test_should_load_all_unloaded_updates
68
+ #initial load was 3 months ago
69
+ initial_run = Date.today.beginning_of_month - 3.months
70
+ DeathMasterFileLoader.new(File.dirname(__FILE__) + '/files/test_dmf_initial_load.txt',initial_run.strftime('%Y-%m-%d')).load_file
71
+ assert DeathMasterFile.count == 5
72
+
73
+ #this will load the last 3 months of files
74
+ #this has been mocked, not really hitting the web site for this
75
+ DeathMasterFileLoader.load_update_files_from_web
76
+ assert DeathMasterFile.count == 8
77
+ assert DeathMasterFile.find_by_as_of(initial_run.to_s(:db))
78
+ assert DeathMasterFile.find_by_as_of((initial_run += 1.month).to_s(:db))
79
+ assert DeathMasterFile.find_by_as_of((initial_run += 1.month).to_s(:db))
80
+ assert DeathMasterFile.find_by_as_of((initial_run += 1.month).to_s(:db))
81
+
82
+
83
+ end
84
+
85
+ end
data/test/test_helper.rb CHANGED
@@ -1,9 +1,23 @@
1
1
  require 'stringio'
2
2
  require 'test/unit'
3
+ require 'mocks/test/death_master_file_loader'
3
4
  require File.dirname(__FILE__) + '/../lib/ssn_validator'
4
5
 
5
6
  ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
6
7
 
8
+
9
+ def setup_high_group_codes_table
10
+ ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
11
+ create_ssn_high_group_codes_table
12
+ end
13
+
14
+ def setup_death_master_file_table
15
+ ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
16
+ create_death_master_file_table
17
+ end
18
+
19
+ private
20
+
7
21
  def create_ssn_high_group_codes_table
8
22
  silence_stream(STDOUT) do
9
23
  ActiveRecord::Schema.define(:version => 1) do
@@ -20,7 +34,26 @@ def create_ssn_high_group_codes_table
20
34
  end
21
35
  end
22
36
 
23
- def setup_high_group_codes_table
24
- ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
25
- create_ssn_high_group_codes_table
37
+ def create_death_master_file_table
38
+ silence_stream(STDOUT) do
39
+ ActiveRecord::Schema.define(:version => 1) do
40
+ create_table :death_master_files do |t|
41
+ t.string :social_security_number
42
+ t.string :last_name
43
+ t.string :name_suffix
44
+ t.string :first_name
45
+ t.string :middle_name
46
+ t.string :verify_proof_code
47
+ t.date :date_of_death
48
+ t.date :date_of_birth
49
+ t.string :state_of_residence
50
+ t.string :last_known_zip_residence
51
+ t.string :last_known_zip_payment
52
+ t.timestamps
53
+ t.date :as_of
54
+ end
55
+ add_index :death_master_files, :social_security_number, :unique => true
56
+ add_index :death_master_files, :as_of
57
+ end
58
+ end
26
59
  end
@@ -1,6 +1,6 @@
1
1
  require File.dirname(__FILE__) + '/test_helper.rb'
2
2
 
3
- class TestSsnHighGroupCode < Test::Unit::TestCase
3
+ class TestSsnHighGroupCodeLoader < Test::Unit::TestCase
4
4
 
5
5
  def setup
6
6
  setup_high_group_codes_table
@@ -8,15 +8,15 @@ class TestSsnHighGroupCode < Test::Unit::TestCase
8
8
 
9
9
  def test_should_load_table_with_current_file
10
10
  assert_equal(0,SsnHighGroupCode.count)
11
- SsnHighGroupCode.load_current_high_group_codes_file
11
+ SsnHighGroupCodeLoader.load_current_high_group_codes_file
12
12
  assert SsnHighGroupCode.count > 0
13
13
  end
14
14
 
15
15
  def test_should_not_load_table_if_as_of_already_in_table
16
- SsnHighGroupCode.load_current_high_group_codes_file
16
+ SsnHighGroupCodeLoader.load_current_high_group_codes_file
17
17
  assert SsnHighGroupCode.count > 0
18
18
  record_count = SsnHighGroupCode.count
19
- SsnHighGroupCode.load_current_high_group_codes_file
19
+ SsnHighGroupCodeLoader.load_current_high_group_codes_file
20
20
  assert_equal(record_count, SsnHighGroupCode.count)
21
21
  end
22
22
  end
@@ -8,13 +8,14 @@ class TestSsnValidator < Test::Unit::TestCase
8
8
  NONDIGIT_SSNS = %w(078051a20 078F51120 78051#20 051,20 078051m20)
9
9
  INVALID_ZEROS_SSNS = %w(166-00-1234 073-96-0000)
10
10
  GROUPS_NOT_ASSIGNED_TO_AREA_SSNS = %w(752991234 755971234 762991254)
11
+ AREA_NOT_ASSIGNED_SSNS = %w(666991234)
11
12
 
12
13
  VALID_SSNS = %w(001021234 161-84-9876 223981111)
13
14
  VALID_INTEGER_SSNS = [115941234, 161849876, 223981111]
14
15
 
15
16
  def setup
16
17
  setup_high_group_codes_table
17
- SsnHighGroupCode.load_current_high_group_codes_file
18
+ SsnHighGroupCodeLoader.load_current_high_group_codes_file
18
19
  end
19
20
 
20
21
  def test_ssn_validations
@@ -48,6 +49,12 @@ class TestSsnValidator < Test::Unit::TestCase
48
49
  assert validator.errors.include?('Invalid group or serial number.'), "Errors: #{validator.errors}"
49
50
  end
50
51
 
52
+ AREA_NOT_ASSIGNED_SSNS.each do |ssn|
53
+ validator = SsnValidator::Ssn.new(ssn)
54
+ assert !validator.valid?
55
+ assert validator.errors.include?("Area '#{validator.area}' has not been assigned."), "Errors: #{validator.errors}"
56
+ end
57
+
51
58
  GROUPS_NOT_ASSIGNED_TO_AREA_SSNS.each do |ssn|
52
59
  validator = SsnValidator::Ssn.new(ssn)
53
60
  assert !validator.valid?
@@ -62,4 +69,15 @@ class TestSsnValidator < Test::Unit::TestCase
62
69
 
63
70
  end
64
71
 
72
+ def test_death_master_file_hit
73
+ create_death_master_file_table
74
+ DeathMasterFileLoader.new(File.dirname(__FILE__) + '/files/test_dmf_initial_load.txt','2009-01-01').load_file
75
+
76
+ validator = SsnValidator::Ssn.new('772781978')#ssn from file
77
+ assert validator.death_master_file_hit?
78
+
79
+ validator = SsnValidator::Ssn.new('666781978')#ssn not in file
80
+ assert !validator.death_master_file_hit?
81
+ end
82
+
65
83
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kevintyll-ssn_validator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Tyll
@@ -11,18 +11,9 @@ cert_chain: []
11
11
 
12
12
  date: 2009-04-13 00:00:00 -07:00
13
13
  default_executable:
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: activerecord
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
20
- requirements:
21
- - - ">"
22
- - !ruby/object:Gem::Version
23
- version: 2.0.0
24
- version:
25
- description:
14
+ dependencies: []
15
+
16
+ description: Validates whether an SSN has likely been issued or not.
26
17
  email: kevintyll@gmail.com
27
18
  executables: []
28
19
 
@@ -38,8 +29,14 @@ files:
38
29
  - Rakefile
39
30
  - generators/ssn_validator_migration/templates/migration.rb
40
31
  - generators/ssn_validator_migration/ssn_validator_migration_generator.rb
32
+ - generators/death_master_file_migration/templates/migration.rb
33
+ - generators/death_master_file_migration/death_master_file_migration_generator.rb
34
+ - lib/ssn_validator/ntis.rb
41
35
  - lib/ssn_validator/models/ssn_high_group_code.rb
36
+ - lib/ssn_validator/models/ssn_high_group_code_loader.rb
42
37
  - lib/ssn_validator/models/ssn_validator.rb
38
+ - lib/ssn_validator/models/death_master_file.rb
39
+ - lib/ssn_validator/models/death_master_file_loader.rb
43
40
  - lib/ssn_validator.rb
44
41
  - lib/tasks/ssn_validator.rake
45
42
  - script/console
@@ -47,10 +44,14 @@ files:
47
44
  - script/generate
48
45
  - test/test_helper.rb
49
46
  - test/test_ssn_validator.rb
50
- - test/test_ssn_high_group_code.rb
47
+ - test/test_ssn_high_group_code_loader.rb
48
+ - test/test_death_master_file_loader.rb
49
+ - test/mocks/test/death_master_file_loader.rb
50
+ - test/files/test_dmf_initial_load.txt
51
+ - test/files/test_dmf_update_load.txt
51
52
  has_rdoc: true
52
- homepage:
53
- post_install_message:
53
+ homepage: http://kevintyll.git.com/ssn_validator
54
+ post_install_message: Read the PostInstall.txt file for instructions on how to get the necessary tables created and data loaded.
54
55
  rdoc_options: []
55
56
 
56
57
  require_paths:
@@ -77,4 +78,8 @@ summary: Validates whether an SSN has likely been issued or not.
77
78
  test_files:
78
79
  - test/test_helper.rb
79
80
  - test/test_ssn_validator.rb
80
- - test/test_ssn_high_group_code.rb
81
+ - test/test_ssn_high_group_code_loader.rb
82
+ - test/test_death_master_file_loader.rb
83
+ - test/mocks/test/death_master_file_loader.rb
84
+ - test/files/test_dmf_initial_load.txt
85
+ - test/files/test_dmf_update_load.txt