Class: ActiveSanction::Sources::UkSanctionsList
- Extended by:
- T::Sig
- Defined in:
- lib/active_sanction/sources/uk_sanctions_list.rb,
lib/active_sanction/sources/uk_sanctions_list/record.rb,
lib/active_sanction/sources/uk_sanctions_list/published_date.rb
Overview
The UK Sanctions List: every designation made under the Sanctions and Anti-Money Laundering Act 2018, published by the Foreign, Commonwealth and Development Office.
snapshot = ActiveSanction::Sources[:uk_sanctions_list].new.sync
This is not the OFSI Consolidated List, and that is deliberate
The list this adapter was scoped against -- OFSI's Consolidated List of
Asset Freeze Targets, ConList.csv -- was retired on 28 January 2026,
when the UK moved every sanctions designation onto one list. Its gov.uk
page is marked withdrawn, and the FCDO's own transition guidance says the
Consolidated List "is no longer being updated".
The blob it was served from still answers 200. That is the trap: an adapter reading it would look healthy on every sync, download a real file, produce real entities and screen a book of business against a list that stopped moving in January. A stale list that reports itself fresh is the most expensive way this library can fail, so the UK adapter reads the list the UK actually publishes.
OFSI Group IDs are not lost in the move: the FCDO carries the historic
one on every designation made before 28 January 2026, and this adapter
keeps it in remarks so a hit can still be reconciled against an OFSI
licence application or a suspected-breach report. Designations made since
carry a UniqueID and nothing else, which is why the Unique ID is what
source_ref is built from.
Seven formats, and why this one
https://sanctionslist.fcdo.gov.uk/docs/UK-Sanctions-List.xml
<Designations><Designation> 6,334 records, 21.8 MB
The FCDO publishes the same list seven ways, and two of them are
machine-readable at this size: the XML above and a CSV added in January
to match the format OFSI's readers had been built against. The CSV is
49.9 MB and 58,424 rows for the same 6,334 designations, because it is a
full cartesian product of every repeating group -- one record in it,
INU0075, occupies 3,780 rows, being 10 names x 7 addresses x 3 phone
numbers x 2 websites x 9 subsidiaries. Reading it means grouping 58,424
rows and then de-duplicating each dimension back out of the product,
which is recoverable but is reconstruction rather than parsing. The XML
states the same structure directly, in 44% of the bytes, against a
published XSD, and it carries a field the CSV has no column for at all.
Conditional GET works here
Unlike the EU's endpoint, this one serves both an ETag and a Last-Modified and honours both: a sync against an unchanged list answers 304 and downloads nothing. Verified against the live endpoint. So the usual run of this source costs one request, and the 21.8 MB is paid only on the days the FCDO republishes.
The two traps in this list
A date component the FCDO does not know is spelled out, not omitted.
dd/mm/1962 is a year-only birth date, dd/07/1978 is a month, and
00/00/1975 is one more spelling of the same thing. 824 of the 3,788
published birth dates -- 22% -- carry a placeholder, and every one of
them reads as nil through an ordinary date parser. See PublishedDate.
The publisher's own script labels disagree with its own strings.
NonLatinScriptType is stated on 2,057 of the 3,856 non-Latin names and
agrees with the characters on 2,054 of them -- but three names labelled
Cyrillic are Latin transliterations (OAO "STUPINSKAYA METALLURGICHESKAYA KOMPANIYA"), and 185 names filed under
<NonLatinName> contain no non-Latin character at all. So script is
left unstated on every name here, which is the same call the UN and EU
adapters make for the same reason: which script a string is in is a
question about its characters, and the normalizer is where that gets
answered. The FCDO's label and language are kept in remarks.
What a clean UK result is worth
More than an EU one. The FCDO marks its own primary name -- every one of
the 6,334 records carries exactly one NameType of Primary Name, six
carry two, and none carries none -- so this adapter never has to guess
which spelling is the official one, which is the judgment the EU adapter
is stuck making. Alias grading is published as a field rather than as
prose, on 2,073 names. Against that, nationality arrives as prose
("Russia", "North Korea"), the way the UN publishes it, and is resolved
by Country at scoring time rather than here.
What this adapter does not do
It does not read <CryptoWalletAddresses> or
<HullIdentificationNumbers>. The XSD defines both and the FCDO populates
neither on any of the 6,334 records published today, and a mapping
written against zero records is a guess that gets discovered to be wrong
by a user rather than by a test. They are the first things to add when
either field appears.
It does not parse PassportAdditionalInformation, which is a sentence
("Afghanistan passport number P04581926, issued on 7 August 2024, issued
in Kandahar, Afghanistan (expires 7 August 2034)") carrying an issuing
country and two dates that Identifier has members for. Reading it is the
same job OFAC's RemarksParser does and wants the same treatment --
a measured coverage figure -- rather than a regex added here in passing.
The sentence is kept verbatim on the identifier's note.
Instance Attribute Summary collapse
-
#warnings ⇒ Array<Parsers::Warning>
readonly
Records that could not be used, and fields that could not be read.
Attributes inherited from Base
#cache, #fetcher, #instrumenter, #logger
Instance Method Summary collapse
- #initialize(*args, **options) ⇒ void constructor
- #parse(raw) ⇒ Array<Entity>
-
#source_version ⇒ String?
07/09/2026-- the date the FCDO stamps on the document and shows on its own download page, which is the string an examiner asking which version a decision was made against will recognise.
Methods inherited from Base
#authority, #column_shapes, #column_tallies, #file_key, #floors, #format, #fresh?, #inspect, #jurisdiction, #key, #parse_argument, published_remarks, #retrieve, #snapshot, #stale?, #sync, #url, #urls
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
Constructor Details
#initialize(*args, **options) ⇒ void
152 153 154 155 156 157 |
# File 'lib/active_sanction/sources/uk_sanctions_list.rb', line 152 def initialize(*args, **) super @warnings = T.let([], T::Array[Parsers::Warning]) @unmapped = T.let([], T::Array[Parsers::Warning]) @generated_at = T.let(nil, T.nilable(String)) end |
Instance Attribute Details
#warnings ⇒ Array<Parsers::Warning> (readonly)
Records that could not be used, and fields that could not be read. Read after #parse; sync orchestration (#34) reports them.
149 150 151 |
# File 'lib/active_sanction/sources/uk_sanctions_list.rb', line 149 def warnings @warnings end |
Instance Method Details
#parse(raw) ⇒ Array<Entity>
160 161 162 163 164 165 |
# File 'lib/active_sanction/sources/uk_sanctions_list.rb', line 160 def parse(raw) reader = LIST.read(raw) entities = build(reader) @warnings = reader.warnings + @unmapped entities end |
#source_version ⇒ String?
07/09/2026 -- the date the FCDO stamps on the document and shows on
its own download page, which is the string an examiner asking which
version a decision was made against will recognise. Falls back to
Last-Modified for a payload handed straight to #snapshot.
172 |
# File 'lib/active_sanction/sources/uk_sanctions_list.rb', line 172 def source_version = @generated_at || super |