Class: ActiveSanction::Sources::OfacConsolidated

Inherits:
Ofac
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/active_sanction/sources/ofac_consolidated.rb,
lib/active_sanction/sources/ofac_consolidated/record.rb

Overview

OFAC's Consolidated (non-SDN) list: everything the US sanctions that is not a Specially Designated National.

snapshot = ActiveSanction::Sources[:ofac_consolidated].new.sync

It is small -- 481 entities against the SDN list's 19,321 -- and it is screened anyway, because being on it still carries real legal weight. A Chinese semiconductor firm on the CMIC list cannot be invested in; a Russian bank on the SSI list can be transacted with but not lent to beyond a tenor the directive sets. Neither is a blocking sanction, and both are prohibitions a compliance team has to act on.

One file, several lists

OFAC ships the non-SDN lists as one set of three CSVs in the same shape as the SDN files -- the reading is Ofac's, and this adapter adds no parsing of its own -- but the rows in it belong to six different lists:

Sectoral Sanctions Identifications (SSI)   the Russia/Ukraine directives
Non-SDN CMIC                               Chinese military-industrial firms
Non-SDN Palestinian Legislative Council    NS-PLC
Non-SDN Menu-Based Sanctions (NS-MBS)      HKAA, CAATSA, EO 14024 directives
CAPTA                                      foreign financial institutions
FSE                                        foreign sanctions evaders

A hit on CMIC and a hit on NS-PLC are different findings with different consequences, so which one a record is on has to survive the parse. #lists answers it for any entity this adapter produced:

ActiveSanction::Sources::OfacConsolidated.lists(entity)   # => [:cmic]
ActiveSanction::Sources::OfacConsolidated.names(entity)   # => ["Non-SDN CMIC List"]

It is a function of entity.programs, which is a canonical Entity member -- so it keeps working on a record that has been stored, serialized and read back, with no per-source column anywhere downstream. The names are also appended to remarks behind the [source fields] marker, which is where a compliance report reads them from.

Why the programs and not a column

There is no list column. CONS_PRIM.CSV has the same twelve columns as SDN.CSV, and the only thing in it that names a list is the program code -- CMIC-EO13959, NS-PLC, HKAA. OFAC states the membership explicitly in CONS_ADVANCED.XML, a 4.5 MB re-publication of the same 481 records, and downloading the list twice to read one attribute off the second copy is a poor trade. So the program code is what LISTS maps.

Checked against that XML's own attribution, the mapping is exact for 478 of the 481 published records. The three it is not are all the same ambiguity, and SHARED is where it lives.

Instance Attribute Summary

Attributes inherited from Base

#cache, #fetcher, #instrumenter, #logger

Class Method Summary collapse

Methods inherited from Base

#authority, #column_shapes, #column_tallies, #file_key, #floors, #format, #fresh?, #initialize, #inspect, #jurisdiction, #key, #parse, #parse_argument, published_remarks, #retrieve, #snapshot, #source_version, #stale?, #sync, #url, #urls, #warnings

Methods included from Definition

#authority, #declarations, #declared?, #declared_floors, #declared_urls, #file_key, #floor, #floors, #format, #jurisdiction, #key, key!, #licence_notice, #licence_url, #multi_url?, #to_h, #url, #urls

Class Method Details

.lists(programs) ⇒ Array<Symbol>

Which sub-lists a record is on, as an Array of the keys NAMES uses. Takes an Entity, or the programs themselves.

OfacConsolidated.lists(entity)                             # => [:ssi]
OfacConsolidated.lists(%w[UKRAINE-EO13662 RUSSIA-EO14024])  # => [:ssi]
OfacConsolidated.lists(%w[RUSSIA-EO14024])                  # => [:ns_mbs]

Empty for a record whose programs name no list this adapter knows, which is what #parse warns about.

Parameters:

  • programs (T.untyped)

Returns:

  • (Array<Symbol>)


161
162
163
164
165
166
167
# File 'lib/active_sanction/sources/ofac_consolidated.rb', line 161

def self.lists(programs)
  codes = programs.respond_to?(:programs) ? programs.programs : Array(programs)
  certain = codes.filter_map { |code| LISTS[code] }.uniq
  shared = codes.filter_map { |code| SHARED[code] }
                .map { |rule| certain.include?(rule[:with]) ? rule[:with] : rule[:alone] }
  (certain + shared).uniq
end

.names(programs) ⇒ Array<String>

The same answer as OFAC spells it, which is what goes in a report.

Parameters:

  • programs (T.untyped)

Returns:

  • (Array<String>)


171
# File 'lib/active_sanction/sources/ofac_consolidated.rb', line 171

def self.names(programs) = lists(programs).map { |list| NAMES.fetch(list) }