summaryrefslogtreecommitdiff
path: root/FS/FS/Misc
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2012-11-06 12:48:41 -0800
committerMark Wells <mark@freeside.biz>2012-11-06 12:49:11 -0800
commit468c9e660eb0edb2033f0f8dbb4458f20280082c (patch)
tree9a87abfbfad9c820598c3a8d773f35c421c2bb38 /FS/FS/Misc
parent2b2aa5664742a134da11862a7cedb37d25524423 (diff)
improved address standardization, #13763
Diffstat (limited to 'FS/FS/Misc')
-rw-r--r--FS/FS/Misc/Geo.pm154
1 files changed, 142 insertions, 12 deletions
diff --git a/FS/FS/Misc/Geo.pm b/FS/FS/Misc/Geo.pm
index 5d6f33c..6bc71fc 100644
--- a/FS/FS/Misc/Geo.pm
+++ b/FS/FS/Misc/Geo.pm
@@ -2,7 +2,7 @@ package FS::Misc::Geo;
use strict;
use base qw( Exporter );
-use vars qw( $DEBUG @EXPORT_OK );
+use vars qw( $DEBUG @EXPORT_OK $conf );
use LWP::UserAgent;
use HTTP::Request;
use HTTP::Request::Common qw( GET POST );
@@ -10,15 +10,19 @@ use HTML::TokeParser;
use URI::Escape 3.31;
use Data::Dumper;
+FS::UID->install_callback( sub {
+ $conf = new FS::Conf;
+} );
+
$DEBUG = 0;
-@EXPORT_OK = qw( get_censustract get_district );
+@EXPORT_OK = qw( get_district );
=head1 NAME
FS::Misc::Geo - routines to fetch geographic information
-=head1 FUNCTIONS
+=head1 CLASS METHODS
=over 4
@@ -30,7 +34,8 @@ codes) or an error message.
=cut
-sub get_censustract {
+sub get_censustract_ffiec {
+ my $class = shift;
my $location = shift;
my $year = shift;
@@ -45,7 +50,7 @@ sub get_censustract {
my $res = $ua->request( GET( $url ) );
warn $res->as_string
- if $DEBUG > 1;
+ if $DEBUG > 2;
unless ($res->code eq '200') {
@@ -87,12 +92,12 @@ sub get_censustract {
btnSearch => 'Search',
);
warn join("\n", @ffiec_args )
- if $DEBUG;
+ if $DEBUG > 1;
push @{ $ua->requests_redirectable }, 'POST';
$res = $ua->request( POST( $url, \@ffiec_args ) );
warn $res->as_string
- if $DEBUG > 1;
+ if $DEBUG > 2;
unless ($res->code eq '200') {
@@ -102,7 +107,7 @@ sub get_censustract {
my @id = qw( MSACode StateCode CountyCode TractCode );
$content = $res->content;
- warn $res->content if $DEBUG > 1;
+ warn $res->content if $DEBUG > 2;
$p = new HTML::TokeParser \$content;
my $prefix = 'UcGeoResult11_lb';
my $compare =
@@ -127,7 +132,7 @@ sub get_censustract {
} #unless ($res->code eq '200')
- return "FFIEC Geocoding error: $error" if $error;
+ die "FFIEC Geocoding error: $error\n" if $error;
$return->{'statecode'} . $return->{'countycode'} . $return->{'tractcode'};
}
@@ -201,12 +206,12 @@ sub wa_sales {
my $query_string = join($delim, @args );
$url .= "?$query_string";
- warn "\nrequest: $url\n\n" if $DEBUG;
+ warn "\nrequest: $url\n\n" if $DEBUG > 1;
my $res = $ua->request( GET( "$url?$query_string" ) );
warn $res->as_string
- if $DEBUG > 1;
+ if $DEBUG > 2;
if ($res->code ne '200') {
$error = $res->message;
@@ -253,7 +258,7 @@ sub wa_sales {
# just to make sure
if ( $return->{'district'} =~ /^\d+$/ and $return->{'tax'} =~ /^.\d+$/ ) {
$return->{'tax'} *= 100; #percentage
- warn Dumper($return) if $DEBUG;
+ warn Dumper($return) if $DEBUG > 1;
return $return;
}
else {
@@ -267,6 +272,131 @@ sub wa_sales {
die "WA tax district lookup error: $error";
}
+sub standardize_usps {
+ my $class = shift;
+
+ eval "use Business::US::USPS::WebTools::AddressStandardization";
+ die $@ if $@;
+
+ my $location = shift;
+ if ( $location->{country} ne 'US' ) {
+ # soft failure
+ warn "standardize_usps not for use in country ".$location->{country}."\n";
+ $location->{addr_clean} = '';
+ return $location;
+ }
+ my $userid = $conf->config('usps_webtools-userid');
+ my $password = $conf->config('usps_webtools-password');
+ my $verifier = Business::US::USPS::WebTools::AddressStandardization->new( {
+ UserID => $userid,
+ Password => $password,
+ Testing => 0,
+ } ) or die "error starting USPS WebTools\n";
+
+ my($zip5, $zip4) = split('-',$location->{'zip'});
+
+ my %usps_args = (
+ FirmName => $location->{company},
+ Address2 => $location->{address1},
+ Address1 => $location->{address2},
+ City => $location->{city},
+ State => $location->{state},
+ Zip5 => $zip5,
+ Zip4 => $zip4,
+ );
+ warn join('', map "$_: $usps_args{$_}\n", keys %usps_args )
+ if $DEBUG > 1;
+
+ my $hash = $verifier->verify_address( %usps_args );
+
+ warn $verifier->response
+ if $DEBUG > 1;
+
+ die "USPS WebTools error: ".$verifier->{error}{description} ."\n"
+ if $verifier->is_error;
+
+ my $zip = $hash->{Zip5};
+ $zip .= '-' . $hash->{Zip4} if $hash->{Zip4} =~ /\d/;
+
+ { company => $hash->{FirmName},
+ address1 => $hash->{Address2},
+ address2 => $hash->{Address1},
+ city => $hash->{City},
+ state => $hash->{State},
+ zip => $zip,
+ country => 'US',
+ addr_clean=> 'Y' }
+}
+
+my %ezlocate_error = ( # USA_Geo_002 documentation
+ 10 => 'State not found',
+ 11 => 'City not found',
+ 12 => 'Invalid street address',
+ 14 => 'Street name not found',
+ 15 => 'Address range does not exist',
+ 16 => 'Ambiguous address',
+ 17 => 'Intersection not found', #unused?
+);
+
+sub standardize_ezlocate {
+ my $self = shift;
+ my $location = shift;
+ my $class;
+ #if ( $location->{country} eq 'US' ) {
+ # $class = 'USA_Geo_004Tool';
+ #}
+ #elsif ( $location->{country} eq 'CA' ) {
+ # $class = 'CAN_Geo_001Tool';
+ #}
+ #else { # shouldn't be a fatal error, just pass through unverified address
+ # warn "standardize_teleatlas: address lookup in '".$location->{country}.
+ # "' not available\n";
+ # return $location;
+ #}
+ #my $path = $conf->config('teleatlas-path') || '';
+ #local @INC = (@INC, $path);
+ #eval "use $class;";
+ #if ( $@ ) {
+ # die "Loading $class failed:\n$@".
+ # "\nMake sure the TeleAtlas Perl SDK is installed correctly.\n";
+ #}
+
+ $class = 'Geo::EZLocate'; # use our own library
+ eval "use $class";
+ die $@ if $@;
+
+ my $userid = $conf->config('ezlocate-userid')
+ or die "no ezlocate-userid configured\n";
+ my $password = $conf->config('ezlocate-password')
+ or die "no ezlocate-password configured\n";
+
+ my $tool = $class->new($userid, $password);
+ my $match = $tool->findAddress(
+ $location->{address1},
+ $location->{city},
+ $location->{state},
+ $location->{zip}, #12345-6789 format is allowed
+ );
+ warn "ezlocate returned match:\n".Dumper($match) if $DEBUG > 1;
+ # error handling - B codes indicate success
+ die $ezlocate_error{$match->{MAT_STAT}}."\n"
+ unless $match->{MAT_STAT} =~ /^B\d$/;
+
+ {
+ address1 => $match->{STD_ADDR},
+ address2 => $location->{address2},
+ city => $match->{STD_CITY},
+ state => $match->{STD_ST},
+ country => $location->{country},
+ zip => $match->{STD_ZIP}.'-'.$match->{STD_P4},
+ latitude => $match->{MAT_LAT},
+ longitude => $match->{MAT_LON},
+ censustract => $match->{FIPS_ST}.$match->{FIPS_CTY}.
+ sprintf('%04.2f',$match->{CEN_TRCT}),
+ addr_clean => 'Y',
+ };
+}
+
=back
=cut