diff --git a/pkg/ofac/mapper.go b/pkg/ofac/mapper.go index 7c084c9b..34329e8d 100644 --- a/pkg/ofac/mapper.go +++ b/pkg/ofac/mapper.go @@ -1,7 +1,9 @@ package ofac import ( + "regexp" "strings" + "time" "github.com/moov-io/watchman/pkg/search" ) @@ -22,22 +24,56 @@ func ToEntity(sdn SDN) search.Entity[SDN] { SourceData: sdn, } + remarks := splitRemarks(sdn.Remarks) + switch strings.ToLower(strings.TrimSpace(sdn.SDNType)) { case "-0-", "": - out.Type = search.EntityBusiness // TODO(adam): or EntityOrganization - // TODO(adam): How to tell Business vs Organization ? + out.Type = search.EntityBusiness + // Set properties + out.Business = &search.Business{ + Name: sdn.SDNName, + } + out.Business.Identifier = makeIdentifiers(remarks, []string{ + "Branch Unit Number", + "Business Number", + "Business Registration Document", + "Business Registration Number", + "Certificate of Incorporation Number", + "Chamber of Commerce Number", + "Chinese Commercial Code", + "Registered Charity No.", + }) case "individual": out.Type = search.EntityPerson out.Person = &search.Person{ - Name: sdn.SDNName, + Name: sdn.SDNName, + Gender: search.Gender(strings.ToLower(firstValue(findMatchingRemarks(remarks, "Gender")))), } + out.Person.BirthDate = withFirstP(findMatchingRemarks(remarks, "DOB"), func(in remark) *time.Time { + // TODO(adam): handle + // DOB 01 Apr 1950 + // DOB 01 Feb 1958 to 28 Feb 1958 + // DOB 1928 + // DOB 1929 to 1930 + // DOB Sep 1958 + // DOB circa 01 Jan 1961 + // DOB circa 1934 + // DOB circa 1979-1982 + + t, _ := time.Parse("02 Jan 2006", in.value) + return &t + }) // TODO(adam): - // DOB 02 Aug 1991; + // citizen Venezuela + // // nationality Russia; - // Gender Male; - // Passport 0291622 (Belize); + // nationality: Eritrean + // + // POB 'Adlun, Lebanon + // Alt. POB: Keren Eritrea + // POB Abadan, Iran case "vessel": out.Type = search.EntityVessel @@ -73,12 +109,171 @@ func ToEntity(sdn SDN) search.Entity[SDN] { } // TODO(adam): - // Aircraft Construction Number (also called L/N or S/N or F/N) 8401; + // Aircraft Construction Number (also called L/N or S/N or F/N) 10907; + // // Aircraft Manufacture Date 1992; + // Aircraft Manufacture Date 01 Dec 1981; + // Aircraft Manufacture Date Apr 1993; + // // Aircraft Model IL76-TD; + // Aircraft Model B.747-422 + // Aircraft Model Gulfstream 200 + // // Aircraft Operator YAS AIR; // Aircraft Manufacturer's Serial Number (MSN) 1023409321; + // + // Previous Aircraft Tail Number 2-WGLP } return out } + +var parenCountryRegex = regexp.MustCompile(`\(([\w\s]+)\)`) + +func makeIdentifier(remarks []string, suffix string) *search.Identifier { + found := findMatchingRemarks(remarks, suffix) + if len(found) == 0 { + return nil + } + + // Often the country is in parenthesis at the end, so let's look for that + // + // Business Number 51566843 (Hong Kong) + country := parenCountryRegex.FindString(found[0].value) + country = strings.TrimPrefix(strings.TrimSuffix(country, ")"), "(") + + return &search.Identifier{ + Name: found[0].fullName, + Country: country, // ISO-3166 // TODO(adam): + Identifier: found[0].value, + } +} + +func makeIdentifiers(remarks []string, needles []string) []search.Identifier { + var out []search.Identifier + for i := range needles { + if id := makeIdentifier(remarks, needles[i]); id != nil { + out = append(out, *id) + } + } + return out +} + +// TODO(adam): +// Drop "alt. " + +// ContactInfo +// Fax: 0097282858208. +// Fax No. (022) 7363196. +// Fax (356)(25990640) +// FAX 850 2 381 4431/4432 +// Alt. Fax: 9221227700019 +// +// Alt. Telephone: 982188554432 +// Telephone (356)(21241232) +// Telephone + 97165749996 +// Telephone +31 010-4951863 +// Telephone No. (022) 7363030 +// Telephone Number: (971) (4) (3248000). +// PHONE 850 2 18111 8204/8208 +// PHONE 850 2 18111 ext. 8221 +// Phone No. 263-4-486946 +// Phone Number 982188526300 +// +// EMAIL daesong@co.chesin.com. +// Email Address EnExchanger@gmail.com +// Email:adelb@shabakah.net.sa. +// info@sanabel.org.uk (email). +// +// Website Oboronlogistika.ru +// Website http://comitet.su/about/ +// http://www.saraproperties.co.uk (website). + +// a.k.a. 'ABU AHMAD ISHAB'. +// a.k.a. 'ZAMANI, Aziz Shah' + +// GovernmentIDs +// +// Cedula No. 94428531 (Colombia) +// Cedula No. 94487319 (Colombia) issued 31 Oct 1994 +// Birth Certificate Number 32270 (Iran) +// Bosnian Personal ID No. 1005967953038 +// British National Overseas Passport 750200421 (United Kingdom) +// C.I.F. B84758374 (Spain). +// C.R. No. 03-B-1620 +// C.R. No. J10/623/1997 (Romania) +// C.U.I.P. AOIR671020H1374898 (Mexico). +// C.U.I.T. 20-60357110-0 (Argentina) +// C.U.R.P. # HESU430525HBCRMR13 (Mexico) +// CNP (Personal Numerical Code) 7460301380011 (Romania) +// Cartilla de Servicio Militar Nacional 607092 (Mexico). +// Citizen's Card Number 210222198011096648 (China). +// Commercial Registry Number 0411518776478 (Iran) +// Commercial Registry Number CH-020.1.066.499-9 (Switzerland) +// Company ID: No. 59 531 at Commercial Registry of the Civil Court of First Instance at Baabda, Lebanon. +// Company Number 05527424 (United Kingdom) +// Company Number IMO 1991835. +// Credencial electoral 073855815496 (Mexico). +// D-U-N-S Number 33-843-5672 +// D.N.I. 00263695-T (Spain) +// Diplomatic Passport 00000017 (Yemen) issued 27 Oct 2008 expires 26 Oct 2014 +// Driver's License No. 04900377 (Moldova) issued 02 Jul 2004 +// Driver's License No. 07442833 (United States) expires 15 Mar 2016 +// Driver's License No. 1-1-22-07-00030905-3 (Guatemala) expires 2010. +// Driver's License No. M600161650080 (United States) issued 07 Apr 2006 expires 08 Jan 2011. +// Driver's License is issued by the State of Texas. +// Dubai Chamber of Commerce Membership No. 123076 (United Arab Emirates). +// Electoral Registry No. 07385114 (Afghanistan). +// Enterprise Number 0430.033.662 (Belgium). +// Fiscal Code 9896460 (Romania). +// Folio Mercantil No. 10328 (Jalisco) (Mexico). +// Government Gazette Number 00132598 (Russia). +// I.F.E. 05116040222575 (Mexico). +// Identification Number 0-16 Reg 53089 (Guatemala) +// Immigration No. A38839964 (United States). +// Interpol: Red Notice. File No. 2009/3599. March 24, 2009. Orange Notice. File No. 2009/52/OS/CCC. February 10, 2009. +// Italian Fiscal Code BCHMHT69R13Z352T. +// Kenyan ID No. 12773667 +// LE Number 07541863 (Peru). +// Legal Entity Number 851683897 (Netherlands) +// License 1249 (Russia). +// Matricula Mercantil No 0000104026 (Colombia). +// N.I.E. X-1552120-B (Spain). +// NIT # 16215230-1 (Colombia). +// National Foreign ID Number 210602197107153012 (China) +// National ID No. (HWI)040182 (Burma) +// Passport #H0044232 (Iraq). +// Passport 00016161 (Yemen) issued 19 Jun 2012 expires 18 Jun 2018 +// Passport No.: 0310857, Eritrea, Issue Date 21 August 2006, Expire Date 20 August 2008) +// Personal ID Card 00246412491303975500493 (Slovenia) expires 17 Dec 2018 +// Pilot License Number 2326384 +// Public Registration Number 1021801434380. +// Public Registration Number 1041202 (Virgin Islands, British) +// R.F.C. # IES-870805 (Mexico). +// RFC AAIJ810808SX4 (Mexico) +// RIF # J-00317392-4 (Venezuela). +// RTN 01019995013319 (Honduras) +// RUC # 1008619-1-537654 (Panama). +// Refugee ID Card A88000043 (Moldova) issued 16 Dec 2005. +// Registration ID 0000421465 (Poland) +// Registration Number 1027700499903 (Russia) +// Residency Number 003-5506420-0100028 (Costa Rica). +// Romanian C.R. J23/242/2004 (Romania). +// Romanian Permanent Resident CAN 0125477 (Romania) issued 13 Jul 2007. +// Romanian Tax Registration 14637977 (Romania). +// SSN 156-92-9858 (United States) +// SSN 33-3208848-3 (Philippines) +// SWIFT/BIC AFABAFKA +// Stateless Person ID Card CC00200261 (Moldova) issued 09 Sep 2000 +// Stateless Person Passport C000375 (Moldova) issued 09 Sep 2000 +// Tax ID No. 002235933 (Canada). +// Trade License No. 04110179 (United Kingdom). +// Travel Document Number A0003900 (Germany) +// Turkish Identificiation Number 10298480866 (Turkey). +// U.S.A. Passport issued 21 Jun 1992 in Amman, Jordan. +// UAE Identification 784-1968-9720837-5 +// UK Company Number 01019769 (United Kingdom) +// US FEIN 000920912 (United States). +// United Social Credit Code Certificate (USCCC) 91420112711981060J (China) +// V.A.T. Number 0430.033.662 (Belgium) +// VisaNumberID 2024702 (Mexico). diff --git a/pkg/ofac/mapper_test.go b/pkg/ofac/mapper_test.go new file mode 100644 index 00000000..c1cbf054 --- /dev/null +++ b/pkg/ofac/mapper_test.go @@ -0,0 +1,43 @@ +package ofac + +import ( + "path/filepath" + "testing" + "time" + + "github.com/moov-io/watchman/pkg/search" + + "github.com/stretchr/testify/require" +) + +func TestMapper(t *testing.T) { + res, err := Read(filepath.Join("..", "..", "test", "testdata", "sdn.csv")) + require.NoError(t, err) + + var sdn *SDN + for i := range res.SDNs { + if res.SDNs[i].EntityID == "15102" { + sdn = res.SDNs[i] + } + } + require.NotNil(t, sdn) + + e := ToEntity(*sdn) + require.Equal(t, "MORENO, Daniel", e.Name) + require.Equal(t, search.EntityPerson, e.Type) + require.Equal(t, search.SourceUSOFAC, e.Source) + + require.NotNil(t, e.Person) + require.Equal(t, "MORENO, Daniel", e.Person.Name) + require.Equal(t, "", string(e.Person.Gender)) + require.Equal(t, "1972-10-12T00:00:00Z", e.Person.BirthDate.Format(time.RFC3339)) + require.Nil(t, e.Person.DeathDate) + require.Len(t, e.Person.GovernmentIDs, 0) + + require.Nil(t, e.Business) + require.Nil(t, e.Organization) + require.Nil(t, e.Aircraft) + require.Nil(t, e.Vessel) + + require.Equal(t, "15102", e.SourceData.EntityID) +} diff --git a/pkg/ofac/reader.go b/pkg/ofac/reader.go index 1503c2da..6e3689bb 100644 --- a/pkg/ofac/reader.go +++ b/pkg/ofac/reader.go @@ -268,8 +268,14 @@ func splitRemarks(input string) []string { return strings.Split(input, ";") } -func findRemarkValues(remarks []string, suffix string) []string { - var out []string +type remark struct { + matchedName string + fullName string + value string +} + +func findMatchingRemarks(remarks []string, suffix string) []remark { + var out []remark if suffix == "" { return out } @@ -283,11 +289,48 @@ func findRemarkValues(remarks []string, suffix string) []string { value = strings.TrimPrefix(value, ":") // identifiers can end with a colon value = strings.TrimSuffix(value, ";") value = strings.TrimSuffix(value, ".") - out = append(out, strings.TrimSpace(value)) + + out = append(out, remark{ + matchedName: suffix, + fullName: remarks[i][:idx+len(suffix)], + value: strings.TrimSpace(value), + }) } return out } +func findRemarkValues(remarks []string, suffix string) []string { + found := findMatchingRemarks(remarks, suffix) + var out []string + for i := range found { + out = append(out, found[i].value) + } + return out +} + +func firstValue(values []remark) string { + if len(values) == 0 { + return "" + } + return values[0].value +} + +func withFirstF[T any](values []remark, f func(remark) T) T { + if len(values) == 0 { + var zero T + return zero + } + return f(values[0]) +} + +func withFirstP[T any](values []remark, f func(remark) *T) *T { + if len(values) == 0 { + var zero T + return &zero + } + return f(values[0]) +} + var ( digitalCurrencies = []string{ "XBT", // Bitcoin diff --git a/pkg/search/models.go b/pkg/search/models.go index 26eb604f..89a65960 100644 --- a/pkg/search/models.go +++ b/pkg/search/models.go @@ -76,7 +76,7 @@ type ContactInfo struct { type GovernmentID struct { Type GovernmentIDType `json:"type"` - Country string `json:"country"` // ISO-3166 + Country string `json:"country"` // ISO-3166 // TODO(adam): Identifier string `json:"identifier"` } @@ -98,17 +98,11 @@ type Business struct { // TODO(adam): Look at OpenSanctions for tax ID codes // https://www.opensanctions.org/reference/#schema.Company type Identifier struct { - Type IdentifierType `json:"type"` - Identifier string `json:"value"` + Name string `json:"string"` + Country string `json:"country"` // ISO-3166 // TODO(adam): + Identifier string `json:"value"` } -type IdentifierType string - -var ( - Identifier_US_EIN IdentifierType = "us_ein" - Identifier_US_SSN IdentifierType = "us_ssn" -) - // Organization // // TODO(adam): https://www.opensanctions.org/reference/#schema.Organization @@ -122,7 +116,7 @@ type Organization struct { type Aircraft struct { Name string `json:"name"` Type AircraftType `json:"type"` - Flag string `json:"flag"` // ISO-3166 + Flag string `json:"flag"` // ISO-3166 // TODO(adam): Built *time.Time `json:"built"` ICAOCode string `json:"icaoCode"` // ICAO aircraft type designator Model string `json:"model"` @@ -143,7 +137,7 @@ type Vessel struct { Name string `json:"name"` IMONumber string `json:"imoNumber"` Type VesselType `json:"type"` - Flag string `json:"flag"` // ISO-3166 + Flag string `json:"flag"` // ISO-3166 // TODO(adam): Built *time.Time `json:"built"` Model string `json:"model"` Tonnage int `json:"tonnage"`