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.
Files changed (143) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +538 -0
  3. data/CODE_OF_CONDUCT.md +74 -0
  4. data/CONTRIBUTING.md +312 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +616 -0
  7. data/SECURITY.md +97 -0
  8. data/active_sanction.gemspec +98 -0
  9. data/docs/adding_a_source.md +1083 -0
  10. data/docs/api_stability.md +357 -0
  11. data/docs/bundle_format.md +407 -0
  12. data/lib/active_sanction/address.rb +170 -0
  13. data/lib/active_sanction/client.rb +341 -0
  14. data/lib/active_sanction/configuration.rb +721 -0
  15. data/lib/active_sanction/countries.txt +292 -0
  16. data/lib/active_sanction/country.rb +164 -0
  17. data/lib/active_sanction/deprecation.rb +168 -0
  18. data/lib/active_sanction/diff/change.rb +208 -0
  19. data/lib/active_sanction/diff.rb +348 -0
  20. data/lib/active_sanction/doctor/checkup.rb +356 -0
  21. data/lib/active_sanction/doctor/diagnosis.rb +245 -0
  22. data/lib/active_sanction/doctor/finding.rb +183 -0
  23. data/lib/active_sanction/doctor/profile.rb +384 -0
  24. data/lib/active_sanction/doctor/report.rb +227 -0
  25. data/lib/active_sanction/doctor.rb +347 -0
  26. data/lib/active_sanction/entity.rb +293 -0
  27. data/lib/active_sanction/error.rb +332 -0
  28. data/lib/active_sanction/fetcher/result.rb +116 -0
  29. data/lib/active_sanction/fetcher.rb +242 -0
  30. data/lib/active_sanction/http_client/errors.rb +94 -0
  31. data/lib/active_sanction/http_client/response.rb +129 -0
  32. data/lib/active_sanction/http_client.rb +324 -0
  33. data/lib/active_sanction/identifier.rb +212 -0
  34. data/lib/active_sanction/index/builder.rb +89 -0
  35. data/lib/active_sanction/index/candidate.rb +63 -0
  36. data/lib/active_sanction/index/entry.rb +66 -0
  37. data/lib/active_sanction/index/features.rb +112 -0
  38. data/lib/active_sanction/index.rb +381 -0
  39. data/lib/active_sanction/match_result.rb +345 -0
  40. data/lib/active_sanction/matcher.rb +388 -0
  41. data/lib/active_sanction/name.rb +183 -0
  42. data/lib/active_sanction/normalizer/cache.rb +82 -0
  43. data/lib/active_sanction/normalizer/dictionaries/honorifics.txt +30 -0
  44. data/lib/active_sanction/normalizer/dictionaries/legal_forms.txt +72 -0
  45. data/lib/active_sanction/normalizer/dictionaries/organization_stopwords.txt +20 -0
  46. data/lib/active_sanction/normalizer/dictionaries/particles.txt +35 -0
  47. data/lib/active_sanction/normalizer/dictionary/stoplist.rb +121 -0
  48. data/lib/active_sanction/normalizer/dictionary.rb +243 -0
  49. data/lib/active_sanction/normalizer/form.rb +279 -0
  50. data/lib/active_sanction/normalizer.rb +137 -0
  51. data/lib/active_sanction/parsers/column_shape.rb +274 -0
  52. data/lib/active_sanction/parsers/delimited_table/reader.rb +194 -0
  53. data/lib/active_sanction/parsers/delimited_table/row.rb +81 -0
  54. data/lib/active_sanction/parsers/delimited_table.rb +151 -0
  55. data/lib/active_sanction/parsers/format.rb +107 -0
  56. data/lib/active_sanction/parsers/join.rb +149 -0
  57. data/lib/active_sanction/parsers/spreadsheet/archive.rb +258 -0
  58. data/lib/active_sanction/parsers/spreadsheet/reader.rb +213 -0
  59. data/lib/active_sanction/parsers/spreadsheet/row.rb +88 -0
  60. data/lib/active_sanction/parsers/spreadsheet/workbook.rb +314 -0
  61. data/lib/active_sanction/parsers/spreadsheet.rb +168 -0
  62. data/lib/active_sanction/parsers/xml_records/backends/nokogiri.rb +134 -0
  63. data/lib/active_sanction/parsers/xml_records/backends/rexml.rb +157 -0
  64. data/lib/active_sanction/parsers/xml_records/backends.rb +103 -0
  65. data/lib/active_sanction/parsers/xml_records/builder.rb +90 -0
  66. data/lib/active_sanction/parsers/xml_records/reader.rb +134 -0
  67. data/lib/active_sanction/parsers/xml_records/record.rb +174 -0
  68. data/lib/active_sanction/parsers/xml_records.rb +140 -0
  69. data/lib/active_sanction/parsers.rb +94 -0
  70. data/lib/active_sanction/partial_date/parser.rb +124 -0
  71. data/lib/active_sanction/partial_date.rb +303 -0
  72. data/lib/active_sanction/payload_cache/checksum.rb +66 -0
  73. data/lib/active_sanction/payload_cache/entry.rb +304 -0
  74. data/lib/active_sanction/payload_cache.rb +366 -0
  75. data/lib/active_sanction/phonetics/double_metaphone.rb +662 -0
  76. data/lib/active_sanction/phonetics.rb +40 -0
  77. data/lib/active_sanction/query.rb +290 -0
  78. data/lib/active_sanction/rescreen/alert.rb +314 -0
  79. data/lib/active_sanction/rescreen.rb +332 -0
  80. data/lib/active_sanction/scorer/adjustments.rb +241 -0
  81. data/lib/active_sanction/scorer/name_score.rb +279 -0
  82. data/lib/active_sanction/scorer/reason.rb +136 -0
  83. data/lib/active_sanction/scorer/result.rb +129 -0
  84. data/lib/active_sanction/scorer/subject.rb +235 -0
  85. data/lib/active_sanction/scorer/weights.rb +306 -0
  86. data/lib/active_sanction/scorer.rb +303 -0
  87. data/lib/active_sanction/similarity/jaro_winkler.rb +210 -0
  88. data/lib/active_sanction/similarity/levenshtein.rb +179 -0
  89. data/lib/active_sanction/similarity/token_set.rb +159 -0
  90. data/lib/active_sanction/similarity/token_sort.rb +120 -0
  91. data/lib/active_sanction/similarity.rb +183 -0
  92. data/lib/active_sanction/snapshot/bundle/header.rb +302 -0
  93. data/lib/active_sanction/snapshot/bundle/payload.rb +185 -0
  94. data/lib/active_sanction/snapshot/bundle/signature.rb +201 -0
  95. data/lib/active_sanction/snapshot/bundle.rb +381 -0
  96. data/lib/active_sanction/snapshot.rb +305 -0
  97. data/lib/active_sanction/sources/australia_dfat/published_date.rb +166 -0
  98. data/lib/active_sanction/sources/australia_dfat/record.rb +319 -0
  99. data/lib/active_sanction/sources/australia_dfat.rb +291 -0
  100. data/lib/active_sanction/sources/base.rb +295 -0
  101. data/lib/active_sanction/sources/canada_sema/record.rb +282 -0
  102. data/lib/active_sanction/sources/canada_sema/source_ref.rb +89 -0
  103. data/lib/active_sanction/sources/canada_sema.rb +153 -0
  104. data/lib/active_sanction/sources/definition.rb +360 -0
  105. data/lib/active_sanction/sources/eu_fsf/record.rb +467 -0
  106. data/lib/active_sanction/sources/eu_fsf.rb +203 -0
  107. data/lib/active_sanction/sources/ofac/record.rb +251 -0
  108. data/lib/active_sanction/sources/ofac/remarks_parser/coverage.rb +120 -0
  109. data/lib/active_sanction/sources/ofac/remarks_parser/vocabulary.rb +121 -0
  110. data/lib/active_sanction/sources/ofac/remarks_parser.rb +312 -0
  111. data/lib/active_sanction/sources/ofac.rb +248 -0
  112. data/lib/active_sanction/sources/ofac_consolidated/record.rb +37 -0
  113. data/lib/active_sanction/sources/ofac_consolidated.rb +203 -0
  114. data/lib/active_sanction/sources/ofac_sdn.rb +43 -0
  115. data/lib/active_sanction/sources/remarks.rb +84 -0
  116. data/lib/active_sanction/sources/uk_sanctions_list/published_date.rb +104 -0
  117. data/lib/active_sanction/sources/uk_sanctions_list/record.rb +412 -0
  118. data/lib/active_sanction/sources/uk_sanctions_list.rb +214 -0
  119. data/lib/active_sanction/sources/un_consolidated/record.rb +279 -0
  120. data/lib/active_sanction/sources/un_consolidated.rb +140 -0
  121. data/lib/active_sanction/sources.rb +196 -0
  122. data/lib/active_sanction/storage/active_record/reader.rb +124 -0
  123. data/lib/active_sanction/storage/active_record/row.rb +175 -0
  124. data/lib/active_sanction/storage/active_record/writer.rb +141 -0
  125. data/lib/active_sanction/storage/active_record.rb +277 -0
  126. data/lib/active_sanction/storage/base.rb +216 -0
  127. data/lib/active_sanction/storage/file_system.rb +373 -0
  128. data/lib/active_sanction/storage/memory.rb +67 -0
  129. data/lib/active_sanction/storage/meta.rb +188 -0
  130. data/lib/active_sanction/storage.rb +74 -0
  131. data/lib/active_sanction/subject.rb +271 -0
  132. data/lib/active_sanction/sync/report.rb +245 -0
  133. data/lib/active_sanction/sync/result.rb +296 -0
  134. data/lib/active_sanction/sync.rb +390 -0
  135. data/lib/active_sanction/validator_store/file_system.rb +103 -0
  136. data/lib/active_sanction/validator_store/memory.rb +41 -0
  137. data/lib/active_sanction/validator_store.rb +119 -0
  138. data/lib/active_sanction/validators.rb +195 -0
  139. data/lib/active_sanction/version.rb +23 -0
  140. data/lib/active_sanction.rb +331 -0
  141. data/lib/generators/active_sanction/install/install_generator.rb +60 -0
  142. data/lib/generators/active_sanction/install/templates/create_active_sanction_tables.rb.tt +142 -0
  143. 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