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 +9 -1
- data/Manifest.txt +11 -1
- data/PostInstall.txt +37 -4
- data/README.rdoc +41 -4
- data/Rakefile +2 -2
- data/generators/death_master_file_migration/death_master_file_migration_generator.rb +12 -0
- data/generators/death_master_file_migration/templates/migration.rb +26 -0
- data/lib/ssn_validator/models/death_master_file.rb +5 -0
- data/lib/ssn_validator/models/death_master_file_loader.rb +235 -0
- data/lib/ssn_validator/models/ssn_high_group_code.rb +1 -77
- data/lib/ssn_validator/models/ssn_high_group_code_loader.rb +80 -0
- data/lib/ssn_validator/models/ssn_validator.rb +7 -1
- data/lib/ssn_validator/ntis.rb +9 -0
- data/lib/ssn_validator.rb +4 -1
- data/lib/tasks/ssn_validator.rake +18 -2
- data/test/files/test_dmf_initial_load.txt +5 -0
- data/test/files/test_dmf_update_load.txt +5 -0
- data/test/mocks/test/death_master_file_loader.rb +35 -0
- data/test/test_death_master_file_loader.rb +85 -0
- data/test/test_helper.rb +36 -3
- data/test/{test_ssn_high_group_code.rb → test_ssn_high_group_code_loader.rb} +4 -4
- data/test/test_ssn_validator.rb +19 -1
- metadata +22 -17
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
|
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/
|
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:
|
5
|
-
|
6
|
-
*
|
7
|
-
|
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
|
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
|
-
|
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,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
|
-
|
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
|
|
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.
|
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
|
-
|
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,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
|
24
|
-
|
25
|
-
|
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
|
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
|
-
|
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
|
-
|
16
|
+
SsnHighGroupCodeLoader.load_current_high_group_codes_file
|
17
17
|
assert SsnHighGroupCode.count > 0
|
18
18
|
record_count = SsnHighGroupCode.count
|
19
|
-
|
19
|
+
SsnHighGroupCodeLoader.load_current_high_group_codes_file
|
20
20
|
assert_equal(record_count, SsnHighGroupCode.count)
|
21
21
|
end
|
22
22
|
end
|
data/test/test_ssn_validator.rb
CHANGED
@@ -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
|
-
|
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.
|
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
|
-
|
16
|
-
|
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/
|
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/
|
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
|