openpay: a PEP match is recorded as a PEP match, not an OFAC sanctions hit #269
7 changed files with 159 additions and 38 deletions
|
|
@ -239,7 +239,7 @@ pub async fn annotate_pep_flags(
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn build_pep_index_from_names(names: &[&str]) -> SanctionsIndex {
|
pub fn build_pep_index_from_names(names: &[&str]) -> SanctionsIndex {
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use op_screening::{EntityType, SanctionedEntity, SanctionsList};
|
use op_screening::{EntityType, SanctionedEntity, WatchList};
|
||||||
|
|
||||||
let entities = names
|
let entities = names
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -258,10 +258,10 @@ pub fn build_pep_index_from_names(names: &[&str]) -> SanctionsIndex {
|
||||||
// "PEP" so callers can distinguish from sanctions programs.
|
// "PEP" so callers can distinguish from sanctions programs.
|
||||||
programs: vec!["PEP".to_string()],
|
programs: vec!["PEP".to_string()],
|
||||||
last_updated: Utc::now(),
|
last_updated: Utc::now(),
|
||||||
// PEP lists aren't issued by treasury authorities, so we
|
// A PEP entry names its own list. It once borrowed
|
||||||
// reuse a sanctions-list discriminant slot for index purposes
|
// `OfacConsolidated`, and every PEP match was then recorded in
|
||||||
// only; the operator-facing surface should not surface this.
|
// the hash-chained audit log as an OFAC sanctions match.
|
||||||
source_list: SanctionsList::OfacConsolidated,
|
source_list: WatchList::Pep,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
SanctionsIndex::build(entities)
|
SanctionsIndex::build(entities)
|
||||||
|
|
@ -312,6 +312,37 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A PEP match is recorded as a PEP match. The PEP index once tagged its
|
||||||
|
/// entries `OfacConsolidated`, and the screener's signed, hash-chained
|
||||||
|
/// audit row then stated an OFAC sanctions match for a politically
|
||||||
|
/// exposed person: a designation where there was a due-diligence flag.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn a_pep_match_is_audited_as_a_pep_match_not_a_sanctions_match() {
|
||||||
|
let pep_screener = Screener::new(
|
||||||
|
build_pep_index_from_names(&["Vladimir Putin"]),
|
||||||
|
ScreenerConfig::default(),
|
||||||
|
AuditLog::new(),
|
||||||
|
);
|
||||||
|
let req = ScreenRequest {
|
||||||
|
name: "Vladimir Putin".into(),
|
||||||
|
dob: None,
|
||||||
|
address: None,
|
||||||
|
additional_ids: vec![],
|
||||||
|
};
|
||||||
|
let res = pep_screener.screen(&req).await.expect("screen");
|
||||||
|
assert!(!res.hits.is_empty(), "the fixture matches its own name");
|
||||||
|
for h in &res.hits {
|
||||||
|
assert_eq!(h.entity.source_list, op_screening::WatchList::Pep);
|
||||||
|
assert!(!h.entity.source_list.is_sanctions());
|
||||||
|
}
|
||||||
|
pep_screener.with_audit_log(|log| {
|
||||||
|
log.verify().expect("the chain verifies");
|
||||||
|
let row = log.entries().last().expect("the screen was audited");
|
||||||
|
let (_, list) = row.top_hit.as_ref().expect("a row with hits names its top hit");
|
||||||
|
assert_eq!(list, "PEP list", "{row:?}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn pep_flagging_fires_on_fixture_list() {
|
async fn pep_flagging_fires_on_fixture_list() {
|
||||||
let pep_idx = build_pep_index_from_names(&["Hugo Chavez", "Vladimir Putin"]);
|
let pep_idx = build_pep_index_from_names(&["Hugo Chavez", "Vladimir Putin"]);
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,10 @@ pub use bsa::{CtrHelper, SarHelper, Transaction, Trigger, TriggerKind};
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
pub use lists::{
|
pub use lists::{
|
||||||
Address, CountryCode, EntityType, Identification, IdentificationKind, SanctionedEntity,
|
Address, CountryCode, EntityType, Identification, IdentificationKind, SanctionedEntity,
|
||||||
SanctionsList,
|
WatchList,
|
||||||
};
|
};
|
||||||
|
#[allow(deprecated)]
|
||||||
|
pub use lists::SanctionsList;
|
||||||
pub use matching::{MatchMethod, MatchScore, MatchedField, screen};
|
pub use matching::{MatchMethod, MatchScore, MatchedField, screen};
|
||||||
pub use normalize::{NormalizedName, normalize};
|
pub use normalize::{NormalizedName, normalize};
|
||||||
pub use screener::{
|
pub use screener::{
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,30 @@
|
||||||
//! Sanctions list domain model.
|
//! Watch-list domain model: sanctions lists, and the PEP and adverse-media
|
||||||
|
//! datasets screened beside them.
|
||||||
//!
|
//!
|
||||||
//! Each authority publishes a slightly different schema, but they all
|
//! Each authority publishes a slightly different schema, but they all
|
||||||
//! agree on the same skeleton: an entity (person, organisation, vessel,
|
//! agree on the same skeleton: an entity (person, organisation, vessel,
|
||||||
//! or aircraft) carries a primary name, aliases, identifying numbers,
|
//! or aircraft) carries a primary name, aliases, identifying numbers,
|
||||||
//! addresses, and one-or-more sanctions programs it is named under.
|
//! addresses, and one-or-more sanctions programs it is named under.
|
||||||
//! [`SanctionedEntity`] is the union we ingest into.
|
//! [`SanctionedEntity`] is the union we ingest into. A PEP or adverse-media
|
||||||
|
//! entry takes the same shape, and its [`WatchList`] says it is not a
|
||||||
|
//! sanctions designation.
|
||||||
|
|
||||||
use chrono::{DateTime, NaiveDate, Utc};
|
use chrono::{DateTime, NaiveDate, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Which authority published the entry.
|
/// The list an entry came from: a sanctions authority's list, or a
|
||||||
|
/// politically-exposed-person or adverse-media dataset.
|
||||||
|
///
|
||||||
|
/// These are different findings, and a match records which it was. A
|
||||||
|
/// sanctions match is a designation (block, report); a PEP match calls for
|
||||||
|
/// enhanced due diligence and is not a designation; an adverse-media match is
|
||||||
|
/// a reason to review. A match is never recorded under a list it did not come
|
||||||
|
/// from: the label of an entry's list is what the hash-chained audit row
|
||||||
|
/// ([`crate::AuditLog`]) states, so a PEP entry tagged with a sanctions
|
||||||
|
/// authority would state a sanctions match that never happened.
|
||||||
|
/// [`WatchList::is_sanctions`] tells the two apart.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub enum SanctionsList {
|
pub enum WatchList {
|
||||||
/// US Treasury — Specially Designated Nationals (SDN).
|
/// US Treasury — Specially Designated Nationals (SDN).
|
||||||
OfacSdn,
|
OfacSdn,
|
||||||
/// US Treasury — Non-SDN Consolidated Sanctions list.
|
/// US Treasury — Non-SDN Consolidated Sanctions list.
|
||||||
|
|
@ -28,9 +41,14 @@ pub enum SanctionsList {
|
||||||
CanadaSema,
|
CanadaSema,
|
||||||
/// Japan Ministry of Finance.
|
/// Japan Ministry of Finance.
|
||||||
JapanMof,
|
JapanMof,
|
||||||
|
/// A politically-exposed-persons dataset (FATF Recommendation 12),
|
||||||
|
/// commercial or operator-maintained. Not a sanctions list.
|
||||||
|
Pep,
|
||||||
|
/// An adverse-media dataset (negative news). Not a sanctions list.
|
||||||
|
AdverseMedia,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SanctionsList {
|
impl WatchList {
|
||||||
/// Human-readable short name.
|
/// Human-readable short name.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn label(self) -> &'static str {
|
pub const fn label(self) -> &'static str {
|
||||||
|
|
@ -43,10 +61,25 @@ impl SanctionsList {
|
||||||
Self::AustraliaDfat => "Australia DFAT",
|
Self::AustraliaDfat => "Australia DFAT",
|
||||||
Self::CanadaSema => "Canada SEMA",
|
Self::CanadaSema => "Canada SEMA",
|
||||||
Self::JapanMof => "Japan MOF",
|
Self::JapanMof => "Japan MOF",
|
||||||
|
Self::Pep => "PEP list",
|
||||||
|
Self::AdverseMedia => "Adverse media",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the list is a sanctions authority's: a match on it is a
|
||||||
|
/// designation. False for [`WatchList::Pep`] and
|
||||||
|
/// [`WatchList::AdverseMedia`].
|
||||||
|
#[must_use]
|
||||||
|
pub const fn is_sanctions(self) -> bool {
|
||||||
|
!matches!(self, Self::Pep | Self::AdverseMedia)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The former name of [`WatchList`], from when it held only sanctions
|
||||||
|
/// authorities.
|
||||||
|
#[deprecated(note = "renamed `WatchList`: it also names PEP and adverse-media lists")]
|
||||||
|
pub type SanctionsList = WatchList;
|
||||||
|
|
||||||
/// What kind of thing the entry names.
|
/// What kind of thing the entry names.
|
||||||
///
|
///
|
||||||
/// Sanctions programs apply to natural persons, legal entities, and to
|
/// Sanctions programs apply to natural persons, legal entities, and to
|
||||||
|
|
@ -140,6 +173,61 @@ pub struct SanctionedEntity {
|
||||||
pub programs: Vec<String>,
|
pub programs: Vec<String>,
|
||||||
/// When the upstream list last updated this entry.
|
/// When the upstream list last updated this entry.
|
||||||
pub last_updated: DateTime<Utc>,
|
pub last_updated: DateTime<Utc>,
|
||||||
/// Which source list this entry came from.
|
/// Which list this entry came from. A PEP or adverse-media entry names
|
||||||
pub source_list: SanctionsList,
|
/// its own list here, never a sanctions authority's.
|
||||||
|
pub source_list: WatchList,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::WatchList;
|
||||||
|
|
||||||
|
/// Every list, in an order the match below keeps complete: a new list
|
||||||
|
/// does not compile until it is here, and so in both tests.
|
||||||
|
const ALL: [WatchList; 10] = [
|
||||||
|
WatchList::OfacSdn,
|
||||||
|
WatchList::OfacConsolidated,
|
||||||
|
WatchList::EuConsolidated,
|
||||||
|
WatchList::UnConsolidated,
|
||||||
|
WatchList::HmtUk,
|
||||||
|
WatchList::AustraliaDfat,
|
||||||
|
WatchList::CanadaSema,
|
||||||
|
WatchList::JapanMof,
|
||||||
|
WatchList::Pep,
|
||||||
|
WatchList::AdverseMedia,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn all_is_complete(l: WatchList) -> usize {
|
||||||
|
match l {
|
||||||
|
WatchList::OfacSdn => 0,
|
||||||
|
WatchList::OfacConsolidated => 1,
|
||||||
|
WatchList::EuConsolidated => 2,
|
||||||
|
WatchList::UnConsolidated => 3,
|
||||||
|
WatchList::HmtUk => 4,
|
||||||
|
WatchList::AustraliaDfat => 5,
|
||||||
|
WatchList::CanadaSema => 6,
|
||||||
|
WatchList::JapanMof => 7,
|
||||||
|
WatchList::Pep => 8,
|
||||||
|
WatchList::AdverseMedia => 9,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The eight authorities are sanctions lists; the PEP and adverse-media
|
||||||
|
/// datasets are not.
|
||||||
|
#[test]
|
||||||
|
fn only_the_authorities_are_sanctions_lists() {
|
||||||
|
for (i, l) in ALL.into_iter().enumerate() {
|
||||||
|
assert_eq!(all_is_complete(l), i);
|
||||||
|
assert_eq!(l.is_sanctions(), !matches!(l, WatchList::Pep | WatchList::AdverseMedia), "{l:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Each list states itself: no two share a label, which is what an
|
||||||
|
/// audit row records.
|
||||||
|
#[test]
|
||||||
|
fn every_list_has_its_own_label() {
|
||||||
|
let labels: std::collections::HashSet<_> = ALL.iter().map(|l| l.label()).collect();
|
||||||
|
assert_eq!(labels.len(), ALL.len());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::audit::AuditLog;
|
use crate::audit::AuditLog;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::lists::{Address, Identification, SanctionsList};
|
use crate::lists::{Address, Identification, WatchList};
|
||||||
use crate::matching::{MatchScore, screen};
|
use crate::matching::{MatchScore, screen};
|
||||||
use crate::storage::SanctionsIndex;
|
use crate::storage::SanctionsIndex;
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ pub struct ScreenerConfig {
|
||||||
/// If true, address country mismatch demotes the result similarly.
|
/// If true, address country mismatch demotes the result similarly.
|
||||||
pub screen_address: bool,
|
pub screen_address: bool,
|
||||||
/// If non-empty, only consider hits whose `source_list` is in the set.
|
/// If non-empty, only consider hits whose `source_list` is in the set.
|
||||||
pub watchlist_filter: HashSet<SanctionsList>,
|
pub watchlist_filter: HashSet<WatchList>,
|
||||||
/// Above-`threshold` but below `hit_threshold` lands in
|
/// Above-`threshold` but below `hit_threshold` lands in
|
||||||
/// `AmbiguousNeedsReview`. Defaults to `0.95`.
|
/// `AmbiguousNeedsReview`. Defaults to `0.95`.
|
||||||
pub hit_threshold: f32,
|
pub hit_threshold: f32,
|
||||||
|
|
@ -231,7 +231,7 @@ mod tests {
|
||||||
identifications: vec![],
|
identifications: vec![],
|
||||||
programs: vec![],
|
programs: vec![],
|
||||||
last_updated: Utc::now(),
|
last_updated: Utc::now(),
|
||||||
source_list: SanctionsList::OfacSdn,
|
source_list: WatchList::OfacSdn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ impl SanctionsIndex {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::lists::{EntityType, SanctionsList};
|
use crate::lists::{EntityType, WatchList};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
|
||||||
fn fake(id: &str, name: &str) -> SanctionedEntity {
|
fn fake(id: &str, name: &str) -> SanctionedEntity {
|
||||||
|
|
@ -199,7 +199,7 @@ mod tests {
|
||||||
identifications: vec![],
|
identifications: vec![],
|
||||||
programs: vec![],
|
programs: vec![],
|
||||||
last_updated: Utc::now(),
|
last_updated: Utc::now(),
|
||||||
source_list: SanctionsList::OfacSdn,
|
source_list: WatchList::OfacSdn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ use serde::Deserialize;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::lists::{
|
use crate::lists::{
|
||||||
CountryCode, EntityType, Identification, IdentificationKind, SanctionedEntity, SanctionsList,
|
CountryCode, EntityType, Identification, IdentificationKind, SanctionedEntity, WatchList,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Boxed future the trait yields. Async fn in traits is stable in
|
/// Boxed future the trait yields. Async fn in traits is stable in
|
||||||
|
|
@ -30,7 +30,7 @@ type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
||||||
/// HTTP client config; refresh cadence is owned by the caller.
|
/// HTTP client config; refresh cadence is owned by the caller.
|
||||||
pub trait ListUpdater: Send + Sync {
|
pub trait ListUpdater: Send + Sync {
|
||||||
/// Which list this updater pulls.
|
/// Which list this updater pulls.
|
||||||
fn list(&self) -> SanctionsList;
|
fn list(&self) -> WatchList;
|
||||||
|
|
||||||
/// Pull the list as it stands right now.
|
/// Pull the list as it stands right now.
|
||||||
fn fetch<'a>(&'a self) -> BoxFuture<'a, Result<Vec<SanctionedEntity>>>;
|
fn fetch<'a>(&'a self) -> BoxFuture<'a, Result<Vec<SanctionedEntity>>>;
|
||||||
|
|
@ -216,14 +216,14 @@ impl OfacEntryBuilder {
|
||||||
identifications: vec![],
|
identifications: vec![],
|
||||||
programs: self.programs,
|
programs: self.programs,
|
||||||
last_updated: Utc::now(),
|
last_updated: Utc::now(),
|
||||||
source_list: SanctionsList::OfacSdn,
|
source_list: WatchList::OfacSdn,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListUpdater for OfacUpdater {
|
impl ListUpdater for OfacUpdater {
|
||||||
fn list(&self) -> SanctionsList {
|
fn list(&self) -> WatchList {
|
||||||
SanctionsList::OfacSdn
|
WatchList::OfacSdn
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch<'a>(&'a self) -> BoxFuture<'a, Result<Vec<SanctionedEntity>>> {
|
fn fetch<'a>(&'a self) -> BoxFuture<'a, Result<Vec<SanctionedEntity>>> {
|
||||||
|
|
@ -396,14 +396,14 @@ impl EuEntryBuilder {
|
||||||
identifications: vec![],
|
identifications: vec![],
|
||||||
programs: vec![],
|
programs: vec![],
|
||||||
last_updated: Utc::now(),
|
last_updated: Utc::now(),
|
||||||
source_list: SanctionsList::EuConsolidated,
|
source_list: WatchList::EuConsolidated,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListUpdater for EuUpdater {
|
impl ListUpdater for EuUpdater {
|
||||||
fn list(&self) -> SanctionsList {
|
fn list(&self) -> WatchList {
|
||||||
SanctionsList::EuConsolidated
|
WatchList::EuConsolidated
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch<'a>(&'a self) -> BoxFuture<'a, Result<Vec<SanctionedEntity>>> {
|
fn fetch<'a>(&'a self) -> BoxFuture<'a, Result<Vec<SanctionedEntity>>> {
|
||||||
|
|
@ -590,14 +590,14 @@ impl UnEntryBuilder {
|
||||||
identifications: vec![],
|
identifications: vec![],
|
||||||
programs: self.programs,
|
programs: self.programs,
|
||||||
last_updated: Utc::now(),
|
last_updated: Utc::now(),
|
||||||
source_list: SanctionsList::UnConsolidated,
|
source_list: WatchList::UnConsolidated,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListUpdater for UnUpdater {
|
impl ListUpdater for UnUpdater {
|
||||||
fn list(&self) -> SanctionsList {
|
fn list(&self) -> WatchList {
|
||||||
SanctionsList::UnConsolidated
|
WatchList::UnConsolidated
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch<'a>(&'a self) -> BoxFuture<'a, Result<Vec<SanctionedEntity>>> {
|
fn fetch<'a>(&'a self) -> BoxFuture<'a, Result<Vec<SanctionedEntity>>> {
|
||||||
|
|
@ -699,7 +699,7 @@ impl HmtUpdater {
|
||||||
identifications: idents,
|
identifications: idents,
|
||||||
programs: r.regime.into_iter().collect(),
|
programs: r.regime.into_iter().collect(),
|
||||||
last_updated: Utc::now(),
|
last_updated: Utc::now(),
|
||||||
source_list: SanctionsList::HmtUk,
|
source_list: WatchList::HmtUk,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
|
|
@ -746,8 +746,8 @@ struct HmtRecord {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListUpdater for HmtUpdater {
|
impl ListUpdater for HmtUpdater {
|
||||||
fn list(&self) -> SanctionsList {
|
fn list(&self) -> WatchList {
|
||||||
SanctionsList::HmtUk
|
WatchList::HmtUk
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch<'a>(&'a self) -> BoxFuture<'a, Result<Vec<SanctionedEntity>>> {
|
fn fetch<'a>(&'a self) -> BoxFuture<'a, Result<Vec<SanctionedEntity>>> {
|
||||||
|
|
@ -797,7 +797,7 @@ mod tests {
|
||||||
assert_eq!(parsed[0].entity_type, EntityType::Individual);
|
assert_eq!(parsed[0].entity_type, EntityType::Individual);
|
||||||
assert_eq!(parsed[0].programs, vec!["SDGT".to_string()]);
|
assert_eq!(parsed[0].programs, vec!["SDGT".to_string()]);
|
||||||
assert_eq!(parsed[1].entity_type, EntityType::Entity);
|
assert_eq!(parsed[1].entity_type, EntityType::Entity);
|
||||||
assert_eq!(parsed[1].source_list, SanctionsList::OfacSdn);
|
assert_eq!(parsed[1].source_list, WatchList::OfacSdn);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -816,7 +816,7 @@ mod tests {
|
||||||
let parsed = UnUpdater::parse_un_xml(xml).expect("parse");
|
let parsed = UnUpdater::parse_un_xml(xml).expect("parse");
|
||||||
assert_eq!(parsed.len(), 1);
|
assert_eq!(parsed.len(), 1);
|
||||||
assert_eq!(parsed[0].name, "Foo Bar");
|
assert_eq!(parsed[0].name, "Foo Bar");
|
||||||
assert_eq!(parsed[0].source_list, SanctionsList::UnConsolidated);
|
assert_eq!(parsed[0].source_list, WatchList::UnConsolidated);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -857,7 +857,7 @@ mod tests {
|
||||||
assert_eq!(parsed.len(), 1);
|
assert_eq!(parsed.len(), 1);
|
||||||
assert_eq!(parsed[0].name, "Ivan Ivanov");
|
assert_eq!(parsed[0].name, "Ivan Ivanov");
|
||||||
assert_eq!(parsed[0].entity_type, EntityType::Individual);
|
assert_eq!(parsed[0].entity_type, EntityType::Individual);
|
||||||
assert_eq!(parsed[0].source_list, SanctionsList::HmtUk);
|
assert_eq!(parsed[0].source_list, WatchList::HmtUk);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parsed[0].dob,
|
parsed[0].dob,
|
||||||
Some(NaiveDate::from_ymd_opt(1970, 1, 1).expect("date"))
|
Some(NaiveDate::from_ymd_opt(1970, 1, 1).expect("date"))
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use op_screening::{
|
use op_screening::{
|
||||||
AuditLog, EntityType, OfacUpdater, SanctionedEntity, SanctionsIndex, SanctionsList,
|
AuditLog, EntityType, OfacUpdater, SanctionedEntity, SanctionsIndex, WatchList,
|
||||||
ScreenDecision, ScreenRequest, Screener, ScreenerConfig,
|
ScreenDecision, ScreenRequest, Screener, ScreenerConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ fn synthetic_entities(n: usize) -> Vec<SanctionedEntity> {
|
||||||
identifications: vec![],
|
identifications: vec![],
|
||||||
programs: vec!["TEST".to_string()],
|
programs: vec!["TEST".to_string()],
|
||||||
last_updated: Utc::now(),
|
last_updated: Utc::now(),
|
||||||
source_list: SanctionsList::OfacSdn,
|
source_list: WatchList::OfacSdn,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Splice in a single distinguished record we can hit.
|
// Splice in a single distinguished record we can hit.
|
||||||
|
|
@ -42,7 +42,7 @@ fn synthetic_entities(n: usize) -> Vec<SanctionedEntity> {
|
||||||
identifications: vec![],
|
identifications: vec![],
|
||||||
programs: vec!["RUSSIA-EO14024".to_string()],
|
programs: vec!["RUSSIA-EO14024".to_string()],
|
||||||
last_updated: Utc::now(),
|
last_updated: Utc::now(),
|
||||||
source_list: SanctionsList::OfacSdn,
|
source_list: WatchList::OfacSdn,
|
||||||
});
|
});
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue