Skip to content

Commit

Permalink
search/ofac: start parsing out remarks field, map to v2 generalized m…
Browse files Browse the repository at this point in the history
…odels
  • Loading branch information
adamdecaf committed Jan 10, 2024
1 parent 07ae5fb commit e5e4f30
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 2 deletions.
84 changes: 84 additions & 0 deletions pkg/ofac/mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package ofac

import (
"strings"

"github.com/moov-io/watchman/pkg/search"
)

func PtrToEntity(sdn *SDN) search.Entity[SDN] {
if sdn != nil {
return ToEntity(*sdn)
}
return search.Entity[SDN]{}
}

// TODO(adam): Accept Addresses, Alts, Comments

func ToEntity(sdn SDN) search.Entity[SDN] {
out := search.Entity[SDN]{
Name: sdn.SDNName,
Source: search.SourceUSOFAC,
SourceData: sdn,
}

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 ?

case "individual":
out.Type = search.EntityPerson
out.Person = &search.Person{
Name: sdn.SDNName,
}

// TODO(adam):
// DOB 02 Aug 1991;
// nationality Russia;
// Gender Male;
// Passport 0291622 (Belize);

case "vessel":
out.Type = search.EntityVessel
out.Vessel = &search.Vessel{
Name: sdn.SDNName,

// IMONumber string `json:"imoNumber"`
// Type VesselType `json:"type"`
// Flag string `json:"flag"` // ISO-3166
// Built *time.Time `json:"built"`
// Model string `json:"model"`
// Tonnage int `json:"tonnage"` // TODO(adam): remove , and ParseInt
// MMSI string `json:"mmsi"` // Maritime Mobile Service Identity
}

// TODO(adam):
// Vessel Registration Identification IMO 9569712;
// MMSI 572469210;
//
// Former Vessel Flag None Identified; alt. Former Vessel Flag Tanzania;

case "aircraft":
out.Type = search.EntityAircraft
out.Aircraft = &search.Aircraft{
Name: sdn.SDNName,

// Type AircraftType `json:"type"`
// Flag string `json:"flag"` // ISO-3166
// Built *time.Time `json:"built"`
// ICAOCode string `json:"icaoCode"` // ICAO aircraft type designator
// Model string `json:"model"`
// SerialNumber string `json:"serialNumber"`
}

// TODO(adam):
// Aircraft Construction Number (also called L/N or S/N or F/N) 8401;
// Aircraft Manufacture Date 1992;
// Aircraft Model IL76-TD;
// Aircraft Operator YAS AIR;
// Aircraft Manufacturer's Serial Number (MSN) 1023409321;
}

return out
}
26 changes: 25 additions & 1 deletion pkg/ofac/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,30 @@ func splitPrograms(in string) []string {
return strings.Split(norm, "; ")
}

func splitRemarks(input string) []string {
return strings.Split(input, ";")
}

func findRemarkValues(remarks []string, suffix string) []string {
var out []string
if suffix == "" {
return out
}
for i := range remarks {
idx := strings.Index(remarks[i], suffix)
if idx == -1 {
continue // not found
}

value := remarks[i][idx+len(suffix):]
value = strings.TrimPrefix(value, ":") // identifiers can end with a colon
value = strings.TrimSuffix(value, ";")
value = strings.TrimSuffix(value, ".")
out = append(out, strings.TrimSpace(value))
}
return out
}

var (
digitalCurrencies = []string{
"XBT", // Bitcoin
Expand Down Expand Up @@ -293,7 +317,7 @@ func readDigitalCurrencyAddresses(remarks string) []DigitalCurrencyAddress {
//
// alt. Digital Currency Address - XBT 12jVCWW1ZhTLA5yVnroEJswqKwsfiZKsax;
//
parts := strings.Split(remarks, ";")
parts := splitRemarks(remarks)
for i := range parts {
// Check if the currency is in the remark
var addressIndex int
Expand Down
45 changes: 45 additions & 0 deletions pkg/ofac/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,51 @@ func TestSDNComments(t *testing.T) {
}
}

func TestSDN__remarks(t *testing.T) {
// individual
remarks := splitRemarks("DOB 12 Oct 1972; POB Corozal, Belize; Passport 0291622 (Belize); Linked To: D'S SUPERMARKET COMPANY LTD.")
expected := []string{"12 Oct 1972"}
require.ElementsMatch(t, expected, findRemarkValues(remarks, "DOB"))
expected = []string{"0291622 (Belize)"}
require.ElementsMatch(t, expected, findRemarkValues(remarks, "Passport"))

// Contact info
remarks = splitRemarks("Website www.nitc.co.ir; Email Address [email protected]; alt. Email Address [email protected]; IFCA Determination - Involved in the Shipping Sector; Additional Sanctions Information - Subject to Secondary Sanctions; Telephone (98)(21)(66153220); Telephone (98)(21)(23803202); Telephone (98)(21)(23803303); Telephone (98)(21)(66153224); Telephone (98)(21)(23802230); Telephone (98)(9121115315); Telephone (98)(9128091642); Telephone (98)(9127389031); Fax (98)(21)(22224537); Fax (98)(21)(23803318); Fax (98)(21)(22013392); Fax (98)(21)(22058763).")
expected = []string{"www.nitc.co.ir"}
require.ElementsMatch(t, expected, findRemarkValues(remarks, "Website"))
expected = []string{"[email protected]", "[email protected]"}
require.ElementsMatch(t, expected, findRemarkValues(remarks, "Email Address"))
expected = []string{"(98)(21)(66153220)", "(98)(21)(23803202)", "(98)(21)(23803303)", "(98)(21)(66153224)", "(98)(21)(23802230)", "(98)(9121115315)", "(98)(9128091642)", "(98)(9127389031)"}
require.ElementsMatch(t, expected, findRemarkValues(remarks, "Telephone"))
expected = []string{"(98)(21)(22224537)", "(98)(21)(23803318)", "(98)(21)(22013392)", "(98)(21)(22058763)"}
require.ElementsMatch(t, expected, findRemarkValues(remarks, "Fax"))

// Vessel
remarks = splitRemarks("Former Vessel Flag Malta; alt. Former Vessel Flag Tuvalu; alt. Former Vessel Flag None Identified; alt. Former Vessel Flag Tanzania; Additional Sanctions Information - Subject to Secondary Sanctions; Vessel Registration Identification IMO 9187629; MMSI 572469210; Linked To: NATIONAL IRANIAN TANKER COMPANY.")
expected = []string{"9187629"}
require.ElementsMatch(t, expected, findRemarkValues(remarks, "Vessel Registration Identification IMO"))
expected = []string{"572469210"}
require.ElementsMatch(t, expected, findRemarkValues(remarks, "MMSI"))

// Aircraft
remarks = splitRemarks("Aircraft Construction Number (also called L/N or S/N or F/N) 8401; Aircraft Manufacture Date 1992; Aircraft Model IL76-TD; Aircraft Operator YAS AIR; Aircraft Manufacturer's Serial Number (MSN) 1023409321; Linked To: POUYA AIR.")
expected = []string{"1992"}
require.ElementsMatch(t, expected, findRemarkValues(remarks, "Manufacture Date"))
expected = []string{"IL76-TD"}
require.ElementsMatch(t, expected, findRemarkValues(remarks, "Model"))
expected = []string{"(MSN) 1023409321"}
require.ElementsMatch(t, expected, findRemarkValues(remarks, "Serial Number"))

t.Run("error conditions", func(t *testing.T) {
remarks = splitRemarks("")
require.Len(t, findRemarkValues(remarks, ""), 0)
require.Len(t, findRemarkValues(remarks, "DOB"), 0)

remarks = splitRemarks(" ; ;;;;; ; ;")
require.Len(t, findRemarkValues(remarks, "DOB"), 0)
})
}

func TestSDNComments_CryptoCurrencies(t *testing.T) {
fd, err := os.CreateTemp("", "sdn-comments")
require.NoError(t, err)
Expand Down
18 changes: 17 additions & 1 deletion pkg/search/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var (
EntityBusiness EntityType = "business"
EntityAircraft EntityType = "aircraft"
EntityVessel EntityType = "vessel"
EntityCryptoAddress EntityType = "crypto-address"
EntityCryptoAddress EntityType = "crypto-address" // TODO(adam): Does this make sense?
)

type SourceList string
Expand Down Expand Up @@ -59,8 +59,24 @@ var (
GenderFemale Gender = "female"
)

type ContactInfo struct {
EmailAddresses []string
PhoneNumbers []string
FaxNumbers []string
}

// TODO(adam):
//
// Website www.tidewaterco.com;
// Email Address [email protected]; alt. Email Address [email protected];
// Telephone: 982188553321; Alt. Telephone: 982188554432;
// Fax: 982188717367; Alt. Fax: 982188708761;
//
// 12803,"TIDEWATER MIDDLE EAST CO.",-0- ,"SDGT] [NPWMD] [IRGC] [IFSR] [IFCA",-0- ,-0- ,-0- ,-0- ,-0- ,-0- ,-0- ," alt. Email Address [email protected]; IFCA Determination - Port Operator; Additional Sanctions Information - Subject to Secondary Sanctions; Business Registration Document # 18745 (Iran); Alt. Fax: 982188708911."

type GovernmentID struct {
Type GovernmentIDType `json:"type"`
Country string `json:"country"` // ISO-3166
Identifier string `json:"identifier"`
}

Expand Down

0 comments on commit e5e4f30

Please sign in to comment.