active_sanction 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +538 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +312 -0
- data/LICENSE.txt +21 -0
- data/README.md +616 -0
- data/SECURITY.md +97 -0
- data/active_sanction.gemspec +98 -0
- data/docs/adding_a_source.md +1083 -0
- data/docs/api_stability.md +357 -0
- data/docs/bundle_format.md +407 -0
- data/lib/active_sanction/address.rb +170 -0
- data/lib/active_sanction/client.rb +341 -0
- data/lib/active_sanction/configuration.rb +721 -0
- data/lib/active_sanction/countries.txt +292 -0
- data/lib/active_sanction/country.rb +164 -0
- data/lib/active_sanction/deprecation.rb +168 -0
- data/lib/active_sanction/diff/change.rb +208 -0
- data/lib/active_sanction/diff.rb +348 -0
- data/lib/active_sanction/doctor/checkup.rb +356 -0
- data/lib/active_sanction/doctor/diagnosis.rb +245 -0
- data/lib/active_sanction/doctor/finding.rb +183 -0
- data/lib/active_sanction/doctor/profile.rb +384 -0
- data/lib/active_sanction/doctor/report.rb +227 -0
- data/lib/active_sanction/doctor.rb +347 -0
- data/lib/active_sanction/entity.rb +293 -0
- data/lib/active_sanction/error.rb +332 -0
- data/lib/active_sanction/fetcher/result.rb +116 -0
- data/lib/active_sanction/fetcher.rb +242 -0
- data/lib/active_sanction/http_client/errors.rb +94 -0
- data/lib/active_sanction/http_client/response.rb +129 -0
- data/lib/active_sanction/http_client.rb +324 -0
- data/lib/active_sanction/identifier.rb +212 -0
- data/lib/active_sanction/index/builder.rb +89 -0
- data/lib/active_sanction/index/candidate.rb +63 -0
- data/lib/active_sanction/index/entry.rb +66 -0
- data/lib/active_sanction/index/features.rb +112 -0
- data/lib/active_sanction/index.rb +381 -0
- data/lib/active_sanction/match_result.rb +345 -0
- data/lib/active_sanction/matcher.rb +388 -0
- data/lib/active_sanction/name.rb +183 -0
- data/lib/active_sanction/normalizer/cache.rb +82 -0
- data/lib/active_sanction/normalizer/dictionaries/honorifics.txt +30 -0
- data/lib/active_sanction/normalizer/dictionaries/legal_forms.txt +72 -0
- data/lib/active_sanction/normalizer/dictionaries/organization_stopwords.txt +20 -0
- data/lib/active_sanction/normalizer/dictionaries/particles.txt +35 -0
- data/lib/active_sanction/normalizer/dictionary/stoplist.rb +121 -0
- data/lib/active_sanction/normalizer/dictionary.rb +243 -0
- data/lib/active_sanction/normalizer/form.rb +279 -0
- data/lib/active_sanction/normalizer.rb +137 -0
- data/lib/active_sanction/parsers/column_shape.rb +274 -0
- data/lib/active_sanction/parsers/delimited_table/reader.rb +194 -0
- data/lib/active_sanction/parsers/delimited_table/row.rb +81 -0
- data/lib/active_sanction/parsers/delimited_table.rb +151 -0
- data/lib/active_sanction/parsers/format.rb +107 -0
- data/lib/active_sanction/parsers/join.rb +149 -0
- data/lib/active_sanction/parsers/spreadsheet/archive.rb +258 -0
- data/lib/active_sanction/parsers/spreadsheet/reader.rb +213 -0
- data/lib/active_sanction/parsers/spreadsheet/row.rb +88 -0
- data/lib/active_sanction/parsers/spreadsheet/workbook.rb +314 -0
- data/lib/active_sanction/parsers/spreadsheet.rb +168 -0
- data/lib/active_sanction/parsers/xml_records/backends/nokogiri.rb +134 -0
- data/lib/active_sanction/parsers/xml_records/backends/rexml.rb +157 -0
- data/lib/active_sanction/parsers/xml_records/backends.rb +103 -0
- data/lib/active_sanction/parsers/xml_records/builder.rb +90 -0
- data/lib/active_sanction/parsers/xml_records/reader.rb +134 -0
- data/lib/active_sanction/parsers/xml_records/record.rb +174 -0
- data/lib/active_sanction/parsers/xml_records.rb +140 -0
- data/lib/active_sanction/parsers.rb +94 -0
- data/lib/active_sanction/partial_date/parser.rb +124 -0
- data/lib/active_sanction/partial_date.rb +303 -0
- data/lib/active_sanction/payload_cache/checksum.rb +66 -0
- data/lib/active_sanction/payload_cache/entry.rb +304 -0
- data/lib/active_sanction/payload_cache.rb +366 -0
- data/lib/active_sanction/phonetics/double_metaphone.rb +662 -0
- data/lib/active_sanction/phonetics.rb +40 -0
- data/lib/active_sanction/query.rb +290 -0
- data/lib/active_sanction/rescreen/alert.rb +314 -0
- data/lib/active_sanction/rescreen.rb +332 -0
- data/lib/active_sanction/scorer/adjustments.rb +241 -0
- data/lib/active_sanction/scorer/name_score.rb +279 -0
- data/lib/active_sanction/scorer/reason.rb +136 -0
- data/lib/active_sanction/scorer/result.rb +129 -0
- data/lib/active_sanction/scorer/subject.rb +235 -0
- data/lib/active_sanction/scorer/weights.rb +306 -0
- data/lib/active_sanction/scorer.rb +303 -0
- data/lib/active_sanction/similarity/jaro_winkler.rb +210 -0
- data/lib/active_sanction/similarity/levenshtein.rb +179 -0
- data/lib/active_sanction/similarity/token_set.rb +159 -0
- data/lib/active_sanction/similarity/token_sort.rb +120 -0
- data/lib/active_sanction/similarity.rb +183 -0
- data/lib/active_sanction/snapshot/bundle/header.rb +302 -0
- data/lib/active_sanction/snapshot/bundle/payload.rb +185 -0
- data/lib/active_sanction/snapshot/bundle/signature.rb +201 -0
- data/lib/active_sanction/snapshot/bundle.rb +381 -0
- data/lib/active_sanction/snapshot.rb +305 -0
- data/lib/active_sanction/sources/australia_dfat/published_date.rb +166 -0
- data/lib/active_sanction/sources/australia_dfat/record.rb +319 -0
- data/lib/active_sanction/sources/australia_dfat.rb +291 -0
- data/lib/active_sanction/sources/base.rb +295 -0
- data/lib/active_sanction/sources/canada_sema/record.rb +282 -0
- data/lib/active_sanction/sources/canada_sema/source_ref.rb +89 -0
- data/lib/active_sanction/sources/canada_sema.rb +153 -0
- data/lib/active_sanction/sources/definition.rb +360 -0
- data/lib/active_sanction/sources/eu_fsf/record.rb +467 -0
- data/lib/active_sanction/sources/eu_fsf.rb +203 -0
- data/lib/active_sanction/sources/ofac/record.rb +251 -0
- data/lib/active_sanction/sources/ofac/remarks_parser/coverage.rb +120 -0
- data/lib/active_sanction/sources/ofac/remarks_parser/vocabulary.rb +121 -0
- data/lib/active_sanction/sources/ofac/remarks_parser.rb +312 -0
- data/lib/active_sanction/sources/ofac.rb +248 -0
- data/lib/active_sanction/sources/ofac_consolidated/record.rb +37 -0
- data/lib/active_sanction/sources/ofac_consolidated.rb +203 -0
- data/lib/active_sanction/sources/ofac_sdn.rb +43 -0
- data/lib/active_sanction/sources/remarks.rb +84 -0
- data/lib/active_sanction/sources/uk_sanctions_list/published_date.rb +104 -0
- data/lib/active_sanction/sources/uk_sanctions_list/record.rb +412 -0
- data/lib/active_sanction/sources/uk_sanctions_list.rb +214 -0
- data/lib/active_sanction/sources/un_consolidated/record.rb +279 -0
- data/lib/active_sanction/sources/un_consolidated.rb +140 -0
- data/lib/active_sanction/sources.rb +196 -0
- data/lib/active_sanction/storage/active_record/reader.rb +124 -0
- data/lib/active_sanction/storage/active_record/row.rb +175 -0
- data/lib/active_sanction/storage/active_record/writer.rb +141 -0
- data/lib/active_sanction/storage/active_record.rb +277 -0
- data/lib/active_sanction/storage/base.rb +216 -0
- data/lib/active_sanction/storage/file_system.rb +373 -0
- data/lib/active_sanction/storage/memory.rb +67 -0
- data/lib/active_sanction/storage/meta.rb +188 -0
- data/lib/active_sanction/storage.rb +74 -0
- data/lib/active_sanction/subject.rb +271 -0
- data/lib/active_sanction/sync/report.rb +245 -0
- data/lib/active_sanction/sync/result.rb +296 -0
- data/lib/active_sanction/sync.rb +390 -0
- data/lib/active_sanction/validator_store/file_system.rb +103 -0
- data/lib/active_sanction/validator_store/memory.rb +41 -0
- data/lib/active_sanction/validator_store.rb +119 -0
- data/lib/active_sanction/validators.rb +195 -0
- data/lib/active_sanction/version.rb +23 -0
- data/lib/active_sanction.rb +331 -0
- data/lib/generators/active_sanction/install/install_generator.rb +60 -0
- data/lib/generators/active_sanction/install/templates/create_active_sanction_tables.rb.tt +142 -0
- metadata +236 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# ISO 3166-1 countries and territories, and the other names sanctions lists
|
|
2
|
+
# write them under.
|
|
3
|
+
#
|
|
4
|
+
# One country per line, fields separated by `|`:
|
|
5
|
+
#
|
|
6
|
+
# <alpha-2>|<alpha-3>|<ISO name>|<alias>|<alias>...
|
|
7
|
+
#
|
|
8
|
+
# Every field on a line resolves to that line's alpha-2 code, so `RU`, `RUS`,
|
|
9
|
+
# `Russian Federation` and `Russia` are one answer. Fields are folded by the
|
|
10
|
+
# same Normalizer::Form a name is folded by, which is why no line here spells
|
|
11
|
+
# a casing, an accent or a punctuation mark: `Cote d'Ivoire` and `CÔTE
|
|
12
|
+
# D'IVOIRE` both reach the `Côte d'Ivoire` below.
|
|
13
|
+
#
|
|
14
|
+
# ### Why the aliases are not optional
|
|
15
|
+
#
|
|
16
|
+
# The ISO name is frequently not what a publisher writes. The UN files
|
|
17
|
+
# `NATIONALITY/VALUE` as free text and OFAC writes nationality into a remarks
|
|
18
|
+
# sentence, so the corpus carries `Iran`, `Syria`, `North Korea` and `Russia`
|
|
19
|
+
# far more often than it carries `Iran, Islamic Republic of` or `Korea,
|
|
20
|
+
# Democratic People's Republic of`. A table with only the ISO names would
|
|
21
|
+
# resolve almost nothing this library actually meets, and an unresolved
|
|
22
|
+
# country is a nationality signal the scorer has to discard.
|
|
23
|
+
#
|
|
24
|
+
# Word order is part of that. Folding does not reorder tokens, so
|
|
25
|
+
# `Korea, Democratic People's Republic of` and `Democratic People's Republic
|
|
26
|
+
# of Korea` are two different strings and both have to be written down.
|
|
27
|
+
#
|
|
28
|
+
# ### What is deliberately not here
|
|
29
|
+
#
|
|
30
|
+
# **Demonyms.** `Russian`, `Egyptian` and `Syrian` are what a person is, not
|
|
31
|
+
# where a country is, and the lists publish the country. Adding them would
|
|
32
|
+
# also put `Chad` and `Chadian` a letter apart from several personal names.
|
|
33
|
+
#
|
|
34
|
+
# **Historical states, except where a list still uses one.** `USSR` and
|
|
35
|
+
# `Soviet Union` are on RU because sanctions records written in the 1990s
|
|
36
|
+
# still say so; `Zaire` is on CD and `Burma` on MM for the same reason.
|
|
37
|
+
#
|
|
38
|
+
# ### Collisions
|
|
39
|
+
#
|
|
40
|
+
# The first line that claims a spelling keeps it, so order in this file
|
|
41
|
+
# decides a tie. There are none among the fields below, and the suite holds
|
|
42
|
+
# the file to that -- a future addition that collides with an existing name
|
|
43
|
+
# fails rather than silently changing what an existing country resolves to.
|
|
44
|
+
AD|AND|Andorra
|
|
45
|
+
AE|ARE|United Arab Emirates|UAE
|
|
46
|
+
AF|AFG|Afghanistan
|
|
47
|
+
AG|ATG|Antigua and Barbuda
|
|
48
|
+
AI|AIA|Anguilla
|
|
49
|
+
AL|ALB|Albania
|
|
50
|
+
AM|ARM|Armenia
|
|
51
|
+
AO|AGO|Angola
|
|
52
|
+
AQ|ATA|Antarctica
|
|
53
|
+
AR|ARG|Argentina
|
|
54
|
+
AS|ASM|American Samoa
|
|
55
|
+
AT|AUT|Austria
|
|
56
|
+
AU|AUS|Australia
|
|
57
|
+
AW|ABW|Aruba
|
|
58
|
+
AX|ALA|Åland Islands
|
|
59
|
+
AZ|AZE|Azerbaijan
|
|
60
|
+
BA|BIH|Bosnia and Herzegovina
|
|
61
|
+
BB|BRB|Barbados
|
|
62
|
+
BD|BGD|Bangladesh
|
|
63
|
+
BE|BEL|Belgium
|
|
64
|
+
BF|BFA|Burkina Faso
|
|
65
|
+
BG|BGR|Bulgaria
|
|
66
|
+
BH|BHR|Bahrain
|
|
67
|
+
BI|BDI|Burundi
|
|
68
|
+
BJ|BEN|Benin
|
|
69
|
+
BL|BLM|Saint Barthélemy
|
|
70
|
+
BM|BMU|Bermuda
|
|
71
|
+
BN|BRN|Brunei Darussalam|Brunei
|
|
72
|
+
BO|BOL|Bolivia, Plurinational State of|Bolivia|Plurinational State of Bolivia
|
|
73
|
+
BQ|BES|Bonaire, Sint Eustatius and Saba
|
|
74
|
+
BR|BRA|Brazil
|
|
75
|
+
BS|BHS|Bahamas|The Bahamas
|
|
76
|
+
BT|BTN|Bhutan
|
|
77
|
+
BV|BVT|Bouvet Island
|
|
78
|
+
BW|BWA|Botswana
|
|
79
|
+
BY|BLR|Belarus|Byelorussia
|
|
80
|
+
BZ|BLZ|Belize
|
|
81
|
+
CA|CAN|Canada
|
|
82
|
+
CC|CCK|Cocos (Keeling) Islands|Keeling Islands
|
|
83
|
+
CD|COD|Congo, Democratic Republic of the|Democratic Republic of the Congo|DR Congo|Zaire|Congo (Democratic Republic)
|
|
84
|
+
CF|CAF|Central African Republic
|
|
85
|
+
CG|COG|Congo|Republic of the Congo|Congo-Brazzaville
|
|
86
|
+
CH|CHE|Switzerland
|
|
87
|
+
CI|CIV|Côte d'Ivoire|Ivory Coast
|
|
88
|
+
CK|COK|Cook Islands
|
|
89
|
+
CL|CHL|Chile
|
|
90
|
+
CM|CMR|Cameroon
|
|
91
|
+
CN|CHN|China|People's Republic of China
|
|
92
|
+
CO|COL|Colombia
|
|
93
|
+
CR|CRI|Costa Rica
|
|
94
|
+
CU|CUB|Cuba
|
|
95
|
+
CV|CPV|Cabo Verde|Cape Verde
|
|
96
|
+
CW|CUW|Curaçao
|
|
97
|
+
CX|CXR|Christmas Island
|
|
98
|
+
CY|CYP|Cyprus
|
|
99
|
+
CZ|CZE|Czechia|Czech Republic
|
|
100
|
+
DE|DEU|Germany|Federal Republic of Germany
|
|
101
|
+
DJ|DJI|Djibouti
|
|
102
|
+
DK|DNK|Denmark
|
|
103
|
+
DM|DMA|Dominica
|
|
104
|
+
DO|DOM|Dominican Republic
|
|
105
|
+
DZ|DZA|Algeria
|
|
106
|
+
EC|ECU|Ecuador
|
|
107
|
+
EE|EST|Estonia
|
|
108
|
+
EG|EGY|Egypt|Arab Republic of Egypt
|
|
109
|
+
EH|ESH|Western Sahara
|
|
110
|
+
ER|ERI|Eritrea
|
|
111
|
+
ES|ESP|Spain
|
|
112
|
+
ET|ETH|Ethiopia
|
|
113
|
+
FI|FIN|Finland
|
|
114
|
+
FJ|FJI|Fiji
|
|
115
|
+
FK|FLK|Falkland Islands|Malvinas
|
|
116
|
+
FM|FSM|Micronesia, Federated States of|Micronesia|Federated States of Micronesia
|
|
117
|
+
FO|FRO|Faroe Islands
|
|
118
|
+
FR|FRA|France
|
|
119
|
+
GA|GAB|Gabon
|
|
120
|
+
GB|GBR|United Kingdom|United Kingdom of Great Britain and Northern Ireland|Great Britain|England|Scotland|Wales|Northern Ireland
|
|
121
|
+
GD|GRD|Grenada
|
|
122
|
+
GE|GEO|Georgia
|
|
123
|
+
GF|GUF|French Guiana
|
|
124
|
+
GG|GGY|Guernsey
|
|
125
|
+
GH|GHA|Ghana
|
|
126
|
+
GI|GIB|Gibraltar
|
|
127
|
+
GL|GRL|Greenland
|
|
128
|
+
GM|GMB|Gambia|The Gambia
|
|
129
|
+
GN|GIN|Guinea
|
|
130
|
+
GP|GLP|Guadeloupe
|
|
131
|
+
GQ|GNQ|Equatorial Guinea
|
|
132
|
+
GR|GRC|Greece
|
|
133
|
+
GS|SGS|South Georgia and the South Sandwich Islands
|
|
134
|
+
GT|GTM|Guatemala
|
|
135
|
+
GU|GUM|Guam
|
|
136
|
+
GW|GNB|Guinea-Bissau
|
|
137
|
+
GY|GUY|Guyana
|
|
138
|
+
HK|HKG|Hong Kong
|
|
139
|
+
HM|HMD|Heard Island and McDonald Islands
|
|
140
|
+
HN|HND|Honduras
|
|
141
|
+
HR|HRV|Croatia
|
|
142
|
+
HT|HTI|Haiti
|
|
143
|
+
HU|HUN|Hungary
|
|
144
|
+
ID|IDN|Indonesia
|
|
145
|
+
IE|IRL|Ireland
|
|
146
|
+
IL|ISR|Israel
|
|
147
|
+
IM|IMN|Isle of Man
|
|
148
|
+
IN|IND|India
|
|
149
|
+
IO|IOT|British Indian Ocean Territory
|
|
150
|
+
IQ|IRQ|Iraq|Republic of Iraq
|
|
151
|
+
IR|IRN|Iran, Islamic Republic of|Iran|Islamic Republic of Iran
|
|
152
|
+
IS|ISL|Iceland
|
|
153
|
+
IT|ITA|Italy
|
|
154
|
+
JE|JEY|Jersey
|
|
155
|
+
JM|JAM|Jamaica
|
|
156
|
+
JO|JOR|Jordan
|
|
157
|
+
JP|JPN|Japan
|
|
158
|
+
KE|KEN|Kenya
|
|
159
|
+
KG|KGZ|Kyrgyzstan
|
|
160
|
+
KH|KHM|Cambodia
|
|
161
|
+
KI|KIR|Kiribati
|
|
162
|
+
KM|COM|Comoros
|
|
163
|
+
KN|KNA|Saint Kitts and Nevis|St Kitts and Nevis
|
|
164
|
+
KP|PRK|Korea, Democratic People's Republic of|Democratic People's Republic of Korea|North Korea|DPRK|Democratic People's Republic of Korea (North Korea)
|
|
165
|
+
KR|KOR|Korea, Republic of|Republic of Korea|South Korea
|
|
166
|
+
KW|KWT|Kuwait
|
|
167
|
+
KY|CYM|Cayman Islands
|
|
168
|
+
KZ|KAZ|Kazakhstan
|
|
169
|
+
LA|LAO|Lao People's Democratic Republic|Laos
|
|
170
|
+
LB|LBN|Lebanon
|
|
171
|
+
LC|LCA|Saint Lucia|St Lucia
|
|
172
|
+
LI|LIE|Liechtenstein
|
|
173
|
+
LK|LKA|Sri Lanka
|
|
174
|
+
LR|LBR|Liberia
|
|
175
|
+
LS|LSO|Lesotho
|
|
176
|
+
LT|LTU|Lithuania
|
|
177
|
+
LU|LUX|Luxembourg
|
|
178
|
+
LV|LVA|Latvia
|
|
179
|
+
LY|LBY|Libya|Libyan Arab Jamahiriya
|
|
180
|
+
MA|MAR|Morocco
|
|
181
|
+
MC|MCO|Monaco
|
|
182
|
+
MD|MDA|Moldova, Republic of|Moldova|Republic of Moldova
|
|
183
|
+
ME|MNE|Montenegro
|
|
184
|
+
MF|MAF|Saint Martin
|
|
185
|
+
MG|MDG|Madagascar
|
|
186
|
+
MH|MHL|Marshall Islands
|
|
187
|
+
MK|MKD|North Macedonia|Macedonia|Republic of North Macedonia|The former Yugoslav Republic of Macedonia
|
|
188
|
+
ML|MLI|Mali
|
|
189
|
+
MM|MMR|Myanmar|Burma
|
|
190
|
+
MN|MNG|Mongolia
|
|
191
|
+
MO|MAC|Macao|Macau
|
|
192
|
+
MP|MNP|Northern Mariana Islands
|
|
193
|
+
MQ|MTQ|Martinique
|
|
194
|
+
MR|MRT|Mauritania
|
|
195
|
+
MS|MSR|Montserrat
|
|
196
|
+
MT|MLT|Malta
|
|
197
|
+
MU|MUS|Mauritius
|
|
198
|
+
MV|MDV|Maldives
|
|
199
|
+
MW|MWI|Malawi
|
|
200
|
+
MX|MEX|Mexico
|
|
201
|
+
MY|MYS|Malaysia
|
|
202
|
+
MZ|MOZ|Mozambique
|
|
203
|
+
NA|NAM|Namibia
|
|
204
|
+
NC|NCL|New Caledonia
|
|
205
|
+
NE|NER|Niger
|
|
206
|
+
NF|NFK|Norfolk Island
|
|
207
|
+
NG|NGA|Nigeria
|
|
208
|
+
NI|NIC|Nicaragua
|
|
209
|
+
NL|NLD|Netherlands|The Netherlands|Holland
|
|
210
|
+
NO|NOR|Norway
|
|
211
|
+
NP|NPL|Nepal
|
|
212
|
+
NR|NRU|Nauru
|
|
213
|
+
NU|NIU|Niue
|
|
214
|
+
NZ|NZL|New Zealand
|
|
215
|
+
OM|OMN|Oman
|
|
216
|
+
PA|PAN|Panama
|
|
217
|
+
PE|PER|Peru
|
|
218
|
+
PF|PYF|French Polynesia
|
|
219
|
+
PG|PNG|Papua New Guinea
|
|
220
|
+
PH|PHL|Philippines
|
|
221
|
+
PK|PAK|Pakistan
|
|
222
|
+
PL|POL|Poland
|
|
223
|
+
PM|SPM|Saint Pierre and Miquelon
|
|
224
|
+
PN|PCN|Pitcairn
|
|
225
|
+
PR|PRI|Puerto Rico
|
|
226
|
+
PS|PSE|Palestine, State of|Palestine|State of Palestine|Palestinian Territory|Occupied Palestinian Territory|Occupied Palestinian Territories|Palestinian
|
|
227
|
+
PT|PRT|Portugal
|
|
228
|
+
PW|PLW|Palau
|
|
229
|
+
PY|PRY|Paraguay
|
|
230
|
+
QA|QAT|Qatar
|
|
231
|
+
RE|REU|Réunion
|
|
232
|
+
RO|ROU|Romania
|
|
233
|
+
RS|SRB|Serbia
|
|
234
|
+
RU|RUS|Russian Federation|Russia|USSR|Soviet Union
|
|
235
|
+
RW|RWA|Rwanda
|
|
236
|
+
SA|SAU|Saudi Arabia
|
|
237
|
+
SB|SLB|Solomon Islands
|
|
238
|
+
SC|SYC|Seychelles
|
|
239
|
+
SD|SDN|Sudan
|
|
240
|
+
SE|SWE|Sweden
|
|
241
|
+
SG|SGP|Singapore
|
|
242
|
+
SH|SHN|Saint Helena, Ascension and Tristan da Cunha|Saint Helena
|
|
243
|
+
SI|SVN|Slovenia
|
|
244
|
+
SJ|SJM|Svalbard and Jan Mayen
|
|
245
|
+
SK|SVK|Slovakia|Slovak Republic
|
|
246
|
+
SL|SLE|Sierra Leone
|
|
247
|
+
SM|SMR|San Marino
|
|
248
|
+
SN|SEN|Senegal
|
|
249
|
+
SO|SOM|Somalia
|
|
250
|
+
SR|SUR|Suriname
|
|
251
|
+
SS|SSD|South Sudan
|
|
252
|
+
ST|STP|Sao Tome and Principe
|
|
253
|
+
SV|SLV|El Salvador
|
|
254
|
+
SX|SXM|Sint Maarten
|
|
255
|
+
SY|SYR|Syrian Arab Republic|Syria
|
|
256
|
+
SZ|SWZ|Eswatini|Swaziland
|
|
257
|
+
TC|TCA|Turks and Caicos Islands
|
|
258
|
+
TD|TCD|Chad
|
|
259
|
+
TF|ATF|French Southern Territories
|
|
260
|
+
TG|TGO|Togo
|
|
261
|
+
TH|THA|Thailand
|
|
262
|
+
TJ|TJK|Tajikistan
|
|
263
|
+
TK|TKL|Tokelau
|
|
264
|
+
TL|TLS|Timor-Leste|East Timor
|
|
265
|
+
TM|TKM|Turkmenistan
|
|
266
|
+
TN|TUN|Tunisia
|
|
267
|
+
TO|TON|Tonga
|
|
268
|
+
TR|TUR|Türkiye|Turkey
|
|
269
|
+
TT|TTO|Trinidad and Tobago
|
|
270
|
+
TV|TUV|Tuvalu
|
|
271
|
+
TW|TWN|Taiwan, Province of China|Taiwan
|
|
272
|
+
TZ|TZA|Tanzania, United Republic of|Tanzania|United Republic of Tanzania
|
|
273
|
+
UA|UKR|Ukraine
|
|
274
|
+
UG|UGA|Uganda
|
|
275
|
+
UM|UMI|United States Minor Outlying Islands
|
|
276
|
+
US|USA|United States|United States of America|America
|
|
277
|
+
UY|URY|Uruguay
|
|
278
|
+
UZ|UZB|Uzbekistan
|
|
279
|
+
VA|VAT|Holy See|Vatican City|Vatican
|
|
280
|
+
VC|VCT|Saint Vincent and the Grenadines|St Vincent
|
|
281
|
+
VE|VEN|Venezuela, Bolivarian Republic of|Venezuela|Bolivarian Republic of Venezuela
|
|
282
|
+
VG|VGB|Virgin Islands, British|British Virgin Islands
|
|
283
|
+
VI|VIR|Virgin Islands, U.S.|United States Virgin Islands
|
|
284
|
+
VN|VNM|Viet Nam|Vietnam
|
|
285
|
+
VU|VUT|Vanuatu
|
|
286
|
+
WF|WLF|Wallis and Futuna
|
|
287
|
+
WS|WSM|Samoa
|
|
288
|
+
YE|YEM|Yemen
|
|
289
|
+
YT|MYT|Mayotte
|
|
290
|
+
ZA|ZAF|South Africa
|
|
291
|
+
ZM|ZMB|Zambia
|
|
292
|
+
ZW|ZWE|Zimbabwe
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
require "active_sanction/normalizer/form"
|
|
7
|
+
|
|
8
|
+
module ActiveSanction
|
|
9
|
+
# Which country a publisher meant, as an ISO 3166-1 alpha-2 code.
|
|
10
|
+
#
|
|
11
|
+
# ActiveSanction::Country.code("Russia") # => "RU"
|
|
12
|
+
# ActiveSanction::Country.code("RUS") # => "RU"
|
|
13
|
+
# ActiveSanction::Country.code("Russian Federation") # => "RU"
|
|
14
|
+
# ActiveSanction::Country.code("Ruritania") # => nil
|
|
15
|
+
#
|
|
16
|
+
# ActiveSanction::Country.name("RU") # => "Russian Federation"
|
|
17
|
+
#
|
|
18
|
+
# ### Why the scorer cannot compare these as strings
|
|
19
|
+
#
|
|
20
|
+
# Nationality is the one identifier on these records that is published as
|
|
21
|
+
# prose. A caller screening a customer has an ISO code in a database column;
|
|
22
|
+
# the UN files `NATIONALITY/VALUE` as whatever its Committee wrote, and OFAC
|
|
23
|
+
# writes nationality into the middle of a remarks sentence. So a query of
|
|
24
|
+
# `RU` meets a record of `Russian Federation`, and a query of `Iran` meets
|
|
25
|
+
# `Iran, Islamic Republic of`.
|
|
26
|
+
#
|
|
27
|
+
# Compared as strings those are three disagreements, and a scorer that reads
|
|
28
|
+
# a disagreement as a conflict does not merely fail to boost the match -- it
|
|
29
|
+
# *penalizes* it, on exactly the records where the caller supplied the most
|
|
30
|
+
# information. That is the worst shape a false negative can take, so the
|
|
31
|
+
# comparison happens on a resolved code or it does not happen at all: see
|
|
32
|
+
# Scorer::Adjustments, which treats an unresolvable country as absent rather
|
|
33
|
+
# than as a conflict.
|
|
34
|
+
#
|
|
35
|
+
# ### What resolution is, and what it is not
|
|
36
|
+
#
|
|
37
|
+
# A lookup, not a guess. Every spelling in `countries.txt` -- the alpha-2,
|
|
38
|
+
# the alpha-3, the ISO name and the aliases a list actually writes -- is
|
|
39
|
+
# folded once at load and mapped to its alpha-2 code, and a string that is
|
|
40
|
+
# not in that table returns nil. There is no fuzzy matching here on purpose:
|
|
41
|
+
# `Niger` and `Nigeria` are two countries, `Guinea` is three, and a scorer
|
|
42
|
+
# that resolved a country approximately would be applying a decisive
|
|
43
|
+
# adjustment on a guess.
|
|
44
|
+
#
|
|
45
|
+
# Input is folded by the same `Normalizer::Form` names are folded by, which
|
|
46
|
+
# is what makes `côte d'ivoire`, `Cote D Ivoire` and `CÔTE-D'IVOIRE` one
|
|
47
|
+
# lookup. It is folded with no stoplist: a country is not an entity, and the
|
|
48
|
+
# organization lists would take `Republic` out of half this table.
|
|
49
|
+
#
|
|
50
|
+
# ### The table is a data file
|
|
51
|
+
#
|
|
52
|
+
# `lib/active_sanction/countries.txt`, one country per line, for the reason
|
|
53
|
+
# the normalizer's dictionaries are files: what belongs on it is settled by
|
|
54
|
+
# reading government lists rather than by reading Ruby, and a contributor
|
|
55
|
+
# adding the spelling their market uses should be sending a one-line diff.
|
|
56
|
+
module Country
|
|
57
|
+
extend T::Sig
|
|
58
|
+
|
|
59
|
+
# @api private
|
|
60
|
+
TABLE = T.let(File.expand_path("countries.txt", __dir__), String)
|
|
61
|
+
|
|
62
|
+
# `<alpha-2>|<alpha-3>|<ISO name>|<alias>...`
|
|
63
|
+
#
|
|
64
|
+
# @api private
|
|
65
|
+
SEPARATOR = T.let("|", String)
|
|
66
|
+
|
|
67
|
+
class << self
|
|
68
|
+
extend T::Sig
|
|
69
|
+
|
|
70
|
+
# The alpha-2 code for a name, a code or an alias, or nil for anything
|
|
71
|
+
# not in the table. nil and blank are nil rather than an error: an
|
|
72
|
+
# absent nationality is the common case on these lists, and the scorer
|
|
73
|
+
# asks about it record by record.
|
|
74
|
+
sig { params(value: T.untyped).returns(T.nilable(String)).checked(:tests) }
|
|
75
|
+
def code(value)
|
|
76
|
+
return nil if value.nil?
|
|
77
|
+
|
|
78
|
+
key = fold(value.to_s)
|
|
79
|
+
key.empty? ? nil : CODES[key]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The ISO name for an alpha-2 code, for a hit a person has to read. The
|
|
83
|
+
# ISO name rather than the alias a publisher wrote, deliberately: the
|
|
84
|
+
# published string is still on the record, and an explanation that names
|
|
85
|
+
# one country two ways is one a reviewer has to reconcile.
|
|
86
|
+
sig { params(value: T.untyped).returns(T.nilable(String)).checked(:tests) }
|
|
87
|
+
def name(value)
|
|
88
|
+
found = code(value)
|
|
89
|
+
found && NAMES[found]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Every alpha-2 code in the table, in file order.
|
|
93
|
+
sig { returns(T::Array[String]).checked(:tests) }
|
|
94
|
+
def codes = NAMES.keys
|
|
95
|
+
|
|
96
|
+
# The fold a lookup key goes through. Public because a collision in the
|
|
97
|
+
# table is a fact about folded strings rather than about the file, and
|
|
98
|
+
# the suite checks for one.
|
|
99
|
+
sig { params(value: String).returns(String).checked(:tests) }
|
|
100
|
+
def fold(value) = Normalizer::Form.new(value).value
|
|
101
|
+
|
|
102
|
+
# Reads the shipped table, or another one shaped like it. Returns the
|
|
103
|
+
# pair the constants below hold: spelling => alpha-2, and alpha-2 =>
|
|
104
|
+
# ISO name.
|
|
105
|
+
#
|
|
106
|
+
# A spelling claimed twice raises rather than resolving to whichever
|
|
107
|
+
# line came first. Two countries quietly sharing a name would move a
|
|
108
|
+
# nationality adjustment onto the wrong record, and it would do it
|
|
109
|
+
# silently -- the table would still load and every lookup would still
|
|
110
|
+
# answer.
|
|
111
|
+
sig { params(path: String).returns([T::Hash[String, String], T::Hash[String, String]]).checked(:tests) }
|
|
112
|
+
def read(path = TABLE)
|
|
113
|
+
codes = {}
|
|
114
|
+
names = {}
|
|
115
|
+
each_row(path) do |fields|
|
|
116
|
+
alpha2 = T.must(fields.first)
|
|
117
|
+
names[alpha2] = fields[2]
|
|
118
|
+
fields.each { |field| claim(codes, field, alpha2) }
|
|
119
|
+
end
|
|
120
|
+
[codes.freeze, names.freeze]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
# One country per line; blank lines and `#` comments ignored, as in the
|
|
126
|
+
# normalizer's dictionaries.
|
|
127
|
+
sig { params(path: String, block: T.proc.params(fields: T::Array[String]).void).void }
|
|
128
|
+
def each_row(path, &block)
|
|
129
|
+
File.readlines(path, chomp: true).each do |line|
|
|
130
|
+
row = line.strip
|
|
131
|
+
next if row.empty? || row.start_with?("#")
|
|
132
|
+
|
|
133
|
+
fields = row.split(SEPARATOR).map(&:strip).reject(&:empty?)
|
|
134
|
+
raise InvalidArgument, "#{path}: a country needs a code, a code and a name: #{row.inspect}" if fields.size < 3
|
|
135
|
+
|
|
136
|
+
block.call(fields)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
sig { params(codes: T::Hash[String, String], field: String, alpha2: String).void }
|
|
141
|
+
def claim(codes, field, alpha2)
|
|
142
|
+
key = fold(field)
|
|
143
|
+
return if key.empty?
|
|
144
|
+
|
|
145
|
+
claimed = codes[key]
|
|
146
|
+
if claimed && claimed != alpha2
|
|
147
|
+
raise InvalidArgument, "#{field.inspect} is claimed by both #{claimed} and #{alpha2}"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
codes[key] = alpha2
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Built at load rather than memoized on first use, so nothing has to
|
|
155
|
+
# synchronize reading a file. Private because the two readers above are
|
|
156
|
+
# the way to reach them.
|
|
157
|
+
codes, names = read
|
|
158
|
+
# @api private
|
|
159
|
+
CODES = T.let(codes, T::Hash[String, String])
|
|
160
|
+
# @api private
|
|
161
|
+
NAMES = T.let(names, T::Hash[String, String])
|
|
162
|
+
private_constant :CODES, :NAMES
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
module ActiveSanction
|
|
7
|
+
# How this library says that something it still supports is going away.
|
|
8
|
+
#
|
|
9
|
+
# ActiveSanction::Deprecation.warn(
|
|
10
|
+
# "ActiveSanction.screen(name:)",
|
|
11
|
+
# replacement: "ActiveSanction.screen(Query.new(...))",
|
|
12
|
+
# since: "1.4.0"
|
|
13
|
+
# )
|
|
14
|
+
# # => active_sanction: ActiveSanction.screen(name:) is deprecated since
|
|
15
|
+
# # 1.4.0 and will be removed in 1.6.0. Use
|
|
16
|
+
# # ActiveSanction.screen(Query.new(...)) instead.
|
|
17
|
+
# # Called from app/jobs/screen_job.rb:31
|
|
18
|
+
#
|
|
19
|
+
# The rules that message is stating are in `docs/api_stability.md`: a
|
|
20
|
+
# deprecated thing keeps working for one full minor release after the one
|
|
21
|
+
# that deprecated it, and the removal version is computed from `since`
|
|
22
|
+
# rather than chosen, so nobody has to remember the policy to apply it.
|
|
23
|
+
#
|
|
24
|
+
# ### Ruby's own switch, rather than one of ours
|
|
25
|
+
#
|
|
26
|
+
# This routes through `Kernel#warn` with `category: :deprecated`, so a host
|
|
27
|
+
# silences it with `Warning[:deprecated] = false` -- the same line that
|
|
28
|
+
# silences every other deprecation in their process, and the line they
|
|
29
|
+
# already have if they run a quiet suite. A `config.deprecation_mode` of our
|
|
30
|
+
# own would be one more thing to discover, and it would not be the thing
|
|
31
|
+
# already sitting in their `spec_helper`.
|
|
32
|
+
#
|
|
33
|
+
# Ruby's default for that switch is off outside verbose mode. That is
|
|
34
|
+
# deliberate on Ruby's part and it is kept: the audience for a deprecation
|
|
35
|
+
# is a developer running `ruby -w`, a test suite or a CI build, and a
|
|
36
|
+
# warning a production process cannot act on is a log line nobody reads.
|
|
37
|
+
#
|
|
38
|
+
# ### Once per call site, not once per call
|
|
39
|
+
#
|
|
40
|
+
# A deprecated method called while looping over 19,000 records would
|
|
41
|
+
# otherwise write 19,000 identical lines. The first call from each source
|
|
42
|
+
# location warns and the rest are silent. `reset!` clears that memory, which
|
|
43
|
+
# is what a spec asserting a deprecation needs and what nothing else should
|
|
44
|
+
# touch.
|
|
45
|
+
module Deprecation
|
|
46
|
+
extend T::Sig
|
|
47
|
+
|
|
48
|
+
# Deprecations overlap for one full minor release. Something deprecated in
|
|
49
|
+
# 1.4.0 goes on working through all of 1.5.x and may be removed in 1.6.0,
|
|
50
|
+
# so an application upgrading one minor at a time always meets the warning
|
|
51
|
+
# at least one release before the breakage.
|
|
52
|
+
#
|
|
53
|
+
# @api private
|
|
54
|
+
OVERLAP_MINORS = 2
|
|
55
|
+
|
|
56
|
+
# Frames to walk past when working out who called a deprecated thing. Two
|
|
57
|
+
# of these would lie: this file is never the answer, and neither is the
|
|
58
|
+
# rest of `lib/` -- a deprecated method's own body is the frame directly
|
|
59
|
+
# beneath the warning, and reporting it would point every deprecation at
|
|
60
|
+
# this library rather than at the line a host has to change.
|
|
61
|
+
#
|
|
62
|
+
# sorbet-runtime is the third, and it is why this is a list rather than a
|
|
63
|
+
# `caller_locations(1, 1)`. Every signed method is called through a
|
|
64
|
+
# validation wrapper, so the immediate caller of anything in `lib/` is
|
|
65
|
+
# Sorbet's `call_validation.rb` -- and that wrapper *moves* once Sorbet
|
|
66
|
+
# swaps in its fast path, so a call site read naively is not even stable
|
|
67
|
+
# between two calls from the same line, which silently defeats the
|
|
68
|
+
# per-site memory above.
|
|
69
|
+
#
|
|
70
|
+
# @api private
|
|
71
|
+
INTERNAL_PATHS = T.let(
|
|
72
|
+
[File.expand_path("..", __dir__),
|
|
73
|
+
Gem.loaded_specs["sorbet-runtime"]&.full_gem_path].compact.freeze,
|
|
74
|
+
T::Array[String]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
@seen = T.let({}, T::Hash[String, TrueClass])
|
|
78
|
+
@mutex = T.let(Mutex.new, Mutex)
|
|
79
|
+
|
|
80
|
+
class << self
|
|
81
|
+
extend T::Sig
|
|
82
|
+
|
|
83
|
+
# @param subject [String] what is deprecated, written the way a caller
|
|
84
|
+
# writes it -- a method signature, a constant, a configuration setting.
|
|
85
|
+
# @param since [String] the released version that deprecated it.
|
|
86
|
+
# @param replacement [String, nil] what to use instead. Nil says there is
|
|
87
|
+
# nothing to move to, which is worth saying out loud rather than
|
|
88
|
+
# leaving somebody to search for one that does not exist.
|
|
89
|
+
# @param removal [String, nil] the version it may be removed in.
|
|
90
|
+
# Computed from `since` when omitted, which is the case that should be
|
|
91
|
+
# normal -- a hand-written removal version is a policy exception, and
|
|
92
|
+
# an exception is worth having to type.
|
|
93
|
+
# @return [void]
|
|
94
|
+
sig do
|
|
95
|
+
params(subject: String, since: String, replacement: T.nilable(String),
|
|
96
|
+
removal: T.nilable(String)).void
|
|
97
|
+
end
|
|
98
|
+
def warn(subject, since:, replacement: nil, removal: nil)
|
|
99
|
+
return unless Warning[:deprecated]
|
|
100
|
+
|
|
101
|
+
site = call_site
|
|
102
|
+
return unless first_time?("#{subject}@#{site}")
|
|
103
|
+
|
|
104
|
+
Kernel.warn(message(subject, since, replacement, removal, site), category: :deprecated)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The version something deprecated in `since` may be removed in: the
|
|
108
|
+
# minor after the next one. A patch release never removes anything, so
|
|
109
|
+
# the patch component is dropped rather than carried forward.
|
|
110
|
+
#
|
|
111
|
+
# @param since [String] the version that deprecated it
|
|
112
|
+
# @return [String] the earliest version it may be removed in
|
|
113
|
+
sig { params(since: String).returns(String) }
|
|
114
|
+
def removal_for(since)
|
|
115
|
+
major, minor = since.split(".").first(2).map(&:to_i)
|
|
116
|
+
"#{major}.#{T.must(minor) + OVERLAP_MINORS}.0"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Forget which call sites have already warned. For a spec that asserts a
|
|
120
|
+
# deprecation fires; nothing in a running application should call it.
|
|
121
|
+
#
|
|
122
|
+
# @return [void]
|
|
123
|
+
sig { void }
|
|
124
|
+
def reset!
|
|
125
|
+
@mutex.synchronize { @seen.clear }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
# The first frame outside this library, which is the line a host would
|
|
131
|
+
# have to change. Says so plainly rather than naming a frame it does not
|
|
132
|
+
# believe.
|
|
133
|
+
sig { returns(String) }
|
|
134
|
+
def call_site
|
|
135
|
+
caller_locations(2, 32)&.each do |location|
|
|
136
|
+
path = location.path
|
|
137
|
+
# Evaluated code has no path, and is not a line anybody can edit.
|
|
138
|
+
next if path.nil?
|
|
139
|
+
next if INTERNAL_PATHS.any? { |internal| path.start_with?(internal) }
|
|
140
|
+
|
|
141
|
+
return "#{path}:#{location.lineno}"
|
|
142
|
+
end
|
|
143
|
+
"an unknown location"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
sig { params(key: String).returns(T::Boolean) }
|
|
147
|
+
def first_time?(key)
|
|
148
|
+
@mutex.synchronize do
|
|
149
|
+
next false if @seen.key?(key)
|
|
150
|
+
|
|
151
|
+
@seen[key] = true
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
sig do
|
|
156
|
+
params(subject: String, since: String, replacement: T.nilable(String),
|
|
157
|
+
removal: T.nilable(String), site: String).returns(String)
|
|
158
|
+
end
|
|
159
|
+
def message(subject, since, replacement, removal, site)
|
|
160
|
+
parts = ["active_sanction: #{subject} is deprecated since #{since} " \
|
|
161
|
+
"and will be removed in #{removal || removal_for(since)}."]
|
|
162
|
+
parts << "Use #{replacement} instead." if replacement
|
|
163
|
+
parts << "Called from #{site}"
|
|
164
|
+
parts.join(" ")
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|