Skip to main content
Version: 4.0.3

Rule support with built-in functions

In the rule file, you can use the following prebuilt functions defined in the RulesUtils.java and CsvCacheHelper.java classes. Rule samples are available in the custom_rules_sample.drl and rules_v1.drl files.

Resolve and Escalate decisions

The functions used in every rule inside the then section to define a fired decision are as follows:

  • resolve("comment", rulesDecision, score) makes a RESOLVE decision.

  • escalate("comment", rulesDecision, score) makes a NO_DECISION decision.

    rule "blacklist_country_strong"
    activation-group "default"
    salience 1700
    when
    eval (countryMatch.blackListed != null && countryMatch.blackListed == true && StringUtils.isNoneBlank(countryMatch.blackListedStr))
    eval (countryMatch.blacklistedSource == BlacklistedSource.COUNTRY)
    then
    RulesUtils.escalate("Blacklisted country strong. Found \"" + countryMatch.blackListedStr + "\" blacklisted country.", rulesDecision);
    end

Field validation

  • isValidStr(String value) checks if a string value is valid.

  • isValidMap(Map<String, String> valueMap) checks if a map is valid.

  • isHieroglyphsChars(String text) returns true if the given string does not contain hieroglyph characters.

    assertTrue(isHieroglyphsChars("Some test"));
    assertTrue(isHieroglyphsChars("eli wool"));
    assertTrue(isHieroglyphsChars("Günter"));
    assertTrue(isHieroglyphsChars("İbrahim Bıçakçı"));

    assertFalse(isHieroglyphsChars("李"));
    assertFalse(isHieroglyphsChars("γλύφω"));
    assertFalse(isHieroglyphsChars("日本人の氏名"));
    assertFalse(isHieroglyphsChars("اسم"));
  • isLatinChars(String text) returns true if the given string contains only Latin characters.

    assertTrue(isLatinChars("Some test"));
    assertTrue(isLatinChars("eli wool"));

    assertFalse(isLatinChars("Günter"));
    assertFalse(isLatinChars("İbrahim Bıçakçı"));
    assertFalse(isLatinChars("李"));
    assertFalse(isLatinChars("γλύφω"));
    assertFalse(isLatinChars("日本人の氏名"));
    assertFalse(isLatinChars("اسم"));

Text normalization

  • String prepareConcatValue(String value) returns text in uppercase, without spaces and punctuation.

    Assertions.assertEquals("MONKEYONTHETREE", RulesUtils.prepareConcatValue(" Monkey:- on\n the tree"));
  • String prepareValue(String value) returns text in uppercase, with trimmed spaces and no punctuation. Punctuation marks are replaced with spaces.

    Assertions.assertEquals("MONKEY ON THE TREE", RulesUtils.prepareValue(" {Monkey]:- on\n the, tree!"));

Text manipulation

  • transliterate(String text) transliterates a given string to Latin characters.

    Assertions.assertEquals("Some test", transliterate("Some test"));
    Assertions.assertEquals("Gunter", transliterate("Günter"));
    Assertions.assertEquals("Ibrahim Bıcakcı", transliterate("İbrahim Bıçakçı"));
    Assertions.assertEquals("li", transliterate("李"));
    Assertions.assertEquals("glypho", transliterate("γλύφω"));
    Assertions.assertEquals("ri ben renno shi ming", transliterate("日本人の氏名"));
    Assertions.assertEquals("asm", transliterate("اسم"));
    Assertions.assertEquals("samy alghamdy", transliterate("سامي الغامدي"));
    Assertions.assertEquals("some data\nOOO 'RIH'", transliterate("some data\nООО 'РИХ'"));
    Assertions.assertEquals(StringUtils.EMPTY, transliterate(null));
    Assertions.assertEquals(StringUtils.EMPTY, transliterate(StringUtils.EMPTY));
  • textContainsValue(String text, String value) checks if text contains a value, using prepareConcatValue as a normalizer.

    assertFalse(RulesUtils.textContainsValue(" Monkey:- on\n the tree", ""));
    assertFalse(RulesUtils.textContainsValue(" Monkey:- on\n the tree", "tree on"));

    assertTrue(RulesUtils.textContainsValue(" Monkey:- on\n the tree", "[the tree]"));
    assertTrue(RulesUtils.textContainsValue(" Monkey:- on\n the tree", "key"));
    assertTrue(RulesUtils.textContainsValue("HBUKGB4BXXX", "GB"));
  • textContainsAnyWleNames(String text, PaymentWatchListEntity wle) checks if text contains any of the names or aliases from the specified payment watchlist entity.

  • isNameInText(String text, String name, boolean tokensOnly) returns true if all name tokens are found in the text. The tokensOnly flag is considered when the name is a single token. Uses prepareValue as a normalizer.

    assertFalse(RulesUtils.isNameInText("", "", true));
    assertFalse(RulesUtils.isNameInText("", "", false));
    assertFalse(RulesUtils.isNameInText(null, null, true));
    assertFalse(RulesUtils.isNameInText(null, null, false));
    assertFalse(RulesUtils.isNameInText("MIRANDA INVESTMENT BANK", "IRAN", false));
    assertFalse(RulesUtils.isNameInText("IRA,NOVA,TABLE", "IRAN", false));
    assertFalse(RulesUtils.isNameInText("WORLD #1, BEST BANK OF IRAN", "IRAN REPUBLIC", false));
    assertFalse(RulesUtils.isNameInText("WORLD #1, BEST BANK OF IRAN", "IR", false));
    assertFalse(RulesUtils.isNameInText("WORLD #1, BEST BANK OF IRAN", "IRN", false));
    assertFalse(RulesUtils.isNameInText("WORLD #1, BEST BANK OF IRAN", "IRN", true));
    assertFalse(RulesUtils.isNameInText("Anna \nIranova", "IRAN", true));

    assertTrue(RulesUtils.isNameInText("WORLD BANK OF /IRAN", "IRAN", false));
    assertTrue(RulesUtils.isNameInText("WORLD BANK IR", "IR", false));
    assertTrue(RulesUtils.isNameInText("WORLD BANK IRN", "IRN", false));
    assertTrue(RulesUtils.isNameInText("WORLD #1, BEST BANK OF [IRAN]", "IRAN BANK", false));
    assertTrue(RulesUtils.isNameInText("WORLD #1, BEST BANK OF IRAN", "best Of Iran", false));
    assertTrue(RulesUtils.isNameInText("FIRST IRANIAN BANK", "IRAN", false));
    assertTrue(RulesUtils.isNameInText("Anna \nIranova", "IRAN", false));
    assertTrue(RulesUtils.isNameInText("WORLD BANK\r\n OF CUBA", "cuba", false));
    assertTrue(RulesUtils.isNameInText("best CUBAN cigars", "cuba", false));
  • isNamesInText(String text, List<String> names, boolean tokensOnly) returns true if all tokens of any name in the list are found in the text. Each name is processed by the isNameInText function above.

  • isNameInTheMiddle(String text, String name) checks whether a given name appears in the middle of the text by verifying that all tokens in the name are located in the central part of the text.

    // Positive cases
    assertTrue(RulesUtils.isNameInTheMiddle("This is John Smith walking.", "John Smith"));
    assertTrue(RulesUtils.isNameInTheMiddle("The amazing (Alice Bob Charlie) is here.", "Alice Bob Charlie"));
    assertTrue(RulesUtils.isNameInTheMiddle("Name order of -John Smith- in the text", "Smith John"));
    assertTrue(RulesUtils.isNameInTheMiddle("123 Main Street, John Smith Building New York", "John Smith"));
    assertTrue(RulesUtils.isNameInTheMiddle("55 Harbor Road Smith Logistics Park John's Office Miami", "John Smith"));
    assertTrue(RulesUtils.isNameInTheMiddle("88 King Street John Logistics Building Toronto", "John Smith"));
    assertTrue(RulesUtils.isNameInTheMiddle("Warehouse District Smith Storage Facility Dallas", "John Smith"));

    // Negative cases
    assertFalse(RulesUtils.isNameInTheMiddle("Missing the name completely.", "John Smith"));
    assertFalse(RulesUtils.isNameInTheMiddle("Alice is in the middle Alice Bob Charlie of her name.", "Alice Bob Charlie"));
    assertFalse(RulesUtils.isNameInTheMiddle("hello to dr. max johnson !", "Max Johnson"));
    assertFalse(RulesUtils.isNameInTheMiddle("John Smith Baker Street 221B, London", "John Smith"));
    assertFalse(RulesUtils.isNameInTheMiddle("742 Evergreen Terrace Springfield Smith", "John Smith"));
    assertFalse(RulesUtils.isNameInTheMiddle("John 500 Market Street Smith San Francisco", "John Smith"));
    assertFalse(RulesUtils.isNameInTheMiddle("77 Smithfield Plaza London", "John Smith")); // Smithfield ≠ Smith
  • hasMultipleTokens(String value) returns true if a string has multiple tokens separated by spaces or punctuation.

    assertTrue(hasMultipleTokens("One two three, four"));
    assertFalse(hasMultipleTokens("OnlyOne"));
  • hasNTokens(String value, int n) returns true if a string is more than N tokens separated by spaces or punctuation.

    assertTrue(hasNTokens("One two three, four", 1));
    assertFalse(hasNTokens("One two three, four", 6));
  • textMatchRegex(String text, String regex, boolean fullMatch) checks whether the given text fully matches or contains a regular expression pattern, with support for the Pattern.DOTALL, Pattern.CASE_INSENSITIVE, and Pattern.MULTILINE flags.

    text = "[TRANSACT   ] FREE_FORMAT\n[CURRENCY ] GBP\nSomeValue\nAnother Line";
    //Exact match (full string)
    assertTrue(RulesUtils.textMatchRegex(text, ".*\\[TRANSACT\\s+\\]\\sFREE_FORMAT.*", true));
    //Substring match (find a FREE_FORMAT line)
    assertTrue(RulesUtils.textMatchRegex(text, "\\[TRANSACT\\s+\\]\\sFREE_FORMAT", false));
    //Match case-insensitively
    assertTrue(RulesUtils.textMatchRegex(text, "free_format", false));
    //Match with anchors (start of line, multiline)
    assertTrue(RulesUtils.textMatchRegex(text, "^SomeValue$", false));
    //Match with DOTALL to span across newlines
    assertTrue(RulesUtils.textMatchRegex("Start\nMiddle\nEnd", "Start.*End", true));
    //Use character classes
    assertTrue(RulesUtils.textMatchRegex("Price: $99.99", "\\$\\d+\\.\\d{2}", false));
  • getMessageContentTagValue(String content, String format, String type, String tag) extracts the value of a specified tag from the provided message content based on the given format and type. You can use this function to check the value of any tag available in the alert.

    String content = """
    {1:XXX0000000000}{2:XXX0000000000}{3:{121:49593459-5241-XXX}}{4:
    :20:XXXXXXX/0016
    :23B:CRED
    :32A:250908USD737,6
    :33B:USD737,6
    :59:/XXX ...
    :70:XXX
    -}
    """;
    assertEquals("CRED", RulesUtils.getMessageContentTagValue(content, "SWIFT_MT", "", "23B"));
    assertEquals("USD737,6", RulesUtils.getMessageContentTagValue(content, "NATIVE", "SWF", "33B"));

Name match (token-based)

  • isNameInclusive(String one, String two, boolean bidirectional) checks if one name token is fully contained within another name token. The boolean parameter controls whether name inclusion works both ways (true). Otherwise (false), it returns true if name two is fully contained within name one.

    assertTrue(isNameInclusive("Silverman Goldstein, Peter Jay", "Peter Goldstein", true));
    assertTrue(isNameInclusive("Silverman Goldstein, Peter Jay", "Peter Silverman", true));
    assertTrue(isNameInclusive("Silverman Goldstein, Peter Jay", "Jay Goldstein", true));
    assertTrue(isNameInclusive("Silverman Goldstein, Peter Jay", "Jay Silverman", true));
    assertTrue(isNameInclusive("Silverman-Goldstein, Peter [Jay]", "Goldstein, Peter", true));
    assertTrue(isNameInclusive("Silverman Goldstein, Peter Jay", "Peter Jay Goldstein", true));
    assertTrue(isNameInclusive("Silverman Goldstein, Peter' Jay", "Peter Jay Goldstein", true));
    assertTrue(isNameInclusive("Silverman Goldstein, Peter Jay", "Jay Peter Goldstein", true));
    assertTrue(isNameInclusive("Silverman Goldstein, Peter Jay", "Peter Jay Silverman", true));
    assertTrue(isNameInclusive("Peter Jay Silverman", "Silverman Goldstein, Peter Jay", true));
    assertTrue(isNameInclusive("Silverman Goldstein, Peter Jay", "Jay Goldstein", false));

    Assertions.assertFalse(isNameInclusive("Silverman Goldstein, Peter Jay", "Peter Jey Silverman", true));
    Assertions.assertFalse(isNameInclusive("Peter (Jey) Silverman", "Silverman Goldstein, Peter Jay", true));
    Assertions.assertFalse(isNameInclusive("Jay Goldstein", "Silverman Goldstein, Peter Jay", false));
  • isNameAliasInText(String text, List<AliasInfo> aliases) checks if any alias name matches the text, using the textContainsValue function for comparison.

    // Test when alias names are not found in the text
    List<AliasInfo> aliases = Arrays.asList(
    new AliasInfo().setName("AliasOne"),
    new AliasInfo().setName("AliasTwo")
    );
    Assertions.assertFalse(RulesUtils.isNameAliasInText("Sample text contains no matches", aliases));

    // Test when one of the alias names is found in the text
    aliases = Arrays.asList(
    new AliasInfo().setName("AliasOne"),
    new AliasInfo().setName("MatchThisAlias")
    );
    Assertions.assertTrue(RulesUtils.isNameAliasInText("Sample text MatchThisAlias found", aliases));
  • smallestNameTokensDifference(String a, String b) calculates the number of token differences between two strings. A token is defined as a substring separated by whitespace. The method prepares the input strings, splits them into tokens, and returns the number of tokens from the smaller set that are not present in the larger set.

    Assertions.assertEquals(0, RulesUtils.smallestTokensDifference("John Doe", "Doe John"));
    Assertions.assertEquals(0, RulesUtils.smallestTokensDifference("John Doe", "Doe, John"));
    Assertions.assertEquals(2, RulesUtils.smallestTokensDifference("John Doe", "Jane Smith"));
    Assertions.assertEquals(2, RulesUtils.smallestTokensDifference("John Doe", "Jane Smith Albert"));
    Assertions.assertEquals(0, RulesUtils.smallestTokensDifference("John , Doe.", "John Doe"));
    Assertions.assertEquals(0, RulesUtils.smallestTokensDifference("John(Albert)", "John Doe Albert"));
    Assertions.assertEquals(1, RulesUtils.smallestTokensDifference("Mary Jane Watson", "Jane Watson, Peter"));
    Assertions.assertEquals(0, RulesUtils.smallestTokensDifference("Mary\nJane ", "Jane-Mary"));
  • nameCommonTokensOverlap(String a, String b) calculates the number of common tokens shared by two input strings. Tokens are obtained by splitting the strings on whitespace, and only unique tokens are considered.

    assertEquals(3, RulesUtils.commonTokensOverlap("John Peter Watson", "Peter John Watson"));
    assertEquals(0, RulesUtils.commonTokensOverlap("John Peter Watson", "Anna Marie"));
    assertEquals(2, RulesUtils.commonTokensOverlap("john peter", "Peter JOHN"));
    assertEquals(1, RulesUtils.commonTokensOverlap("John Watson", "Watson Smith"));
  • float nameCommonTokensRatio(String a, String b) calculates the token overlap ratio between two input strings. The method splits the strings into tokens, compares the resulting token sets, and computes the ratio of common tokens to the total number of tokens.

    assertEquals(1.0, RulesUtils.commonTokensRatio("John Peter Watson", "Peter John Watson"));
    assertEquals(0.0, RulesUtils.commonTokensRatio("John Peter Watson", "Anna Marie"));
    assertEquals(1.0, RulesUtils.commonTokensRatio("john peter", "Peter JOHN"), 0.0001f);
    assertEquals(0.5, RulesUtils.commonTokensRatio("John Watson", "Watson Smith"), 0.0001f);
    assertEquals(0.4, RulesUtils.commonTokensRatio("John Jay Watson", "Watson Smith"), 0.0001f);
  • nameCommonDirectionalTokensRatio(String a, String b) compares the tokens in both strings and calculates the ratio of common tokens relative to the tokens in the first string.

    assertEquals(1.0f, RulesUtils.nameCommonDirectionalTokensRatio("Alice Bob Charlie", "Charlie, Alice Bob "));
    assertEquals(1.0f, RulesUtils.nameCommonDirectionalTokensRatio("Alice", "Alice"));

    // Test with partial matches
    assertEquals(0.5f, RulesUtils.nameCommonDirectionalTokensRatio("Alice Bob", "Alice Charlie"), 0.01f);
    assertEquals(0.33f, RulesUtils.nameCommonDirectionalTokensRatio("Alice Bob Charlie", "Alice Dave"), 0.01f);

    // Test with completely unrelated strings
    assertEquals(0.0f, RulesUtils.nameCommonDirectionalTokensRatio("Alice Bob", "Mike Dave"));
    assertEquals(0.0f, RulesUtils.nameCommonDirectionalTokensRatio("Charlie", "David"));

    // Test with null or empty strings
    assertEquals(0.0f, RulesUtils.nameCommonDirectionalTokensRatio(null, "Alice Bob"));
    assertEquals(0.0f, RulesUtils.nameCommonDirectionalTokensRatio("Alice Bob", null));
    assertEquals(0.0f, RulesUtils.nameCommonDirectionalTokensRatio("", "Alice Bob"));
    assertEquals(0.0f, RulesUtils.nameCommonDirectionalTokensRatio("Alice Bob", ""));
  • int nameTokensDifference(String name, PaymentWatchListEntity wle, boolean useAliases) calculates the token difference between a given name and the name or aliases of a payment watchlist entity. The result is the smallest token difference found across the provided name and the available aliases.

    PaymentWatchListEntity wle = new PaymentWatchListEntity().setName("John Smith").setPrimaryName("John Jay Smith");

    // Test with an identical WLE primary name, no aliases
    assertEquals(0, RulesUtils.nameTokensDifference("John Jay Smith Jr.", wle, false));
    // Test with an identical WLE name (reflects the best alias), no aliases
    assertEquals(1, RulesUtils.nameTokensDifference("John Smith Jr.", wle, false));
    // Test with an identical WLE name (reflects the best alias), no aliases, and no primary name
    wle.setPrimaryName("");
    assertEquals(0, RulesUtils.nameTokensDifference("John Smith Jr.", wle, false));

    // Test with slight differences
    wle.setName("Jonathan Smith").setPrimaryName("John Smith");
    assertEquals(1, RulesUtils.nameTokensDifference("Joe Smith", wle, false));

    // Test with aliases
    wle.setAliases(Arrays.asList(
    new AliasInfo().setName("Jon Smith"),
    new AliasInfo().setName("Johnny Smith")));
    assertEquals(0, RulesUtils.nameTokensDifference("John Smith", wle, true));
    assertEquals(1, RulesUtils.nameTokensDifference("John Doe", wle, true));
  • int nameTokensOverlap(String name, PaymentWatchListEntity wle, boolean useAliases) computes the maximum token overlap between the given name and the name fields of a payment watchlist entity, including its primary name, name, and aliases, if applicable.

    PaymentWatchListEntity wle = new PaymentWatchListEntity().setName("John Smith").setPrimaryName("Jonathan Smith");
    wle.setAliases(Arrays.asList(
    new AliasInfo().setName("Jon Smith"),
    new AliasInfo().setName("Johnny Smith")
    ));

    // Test with an exact match on the name (the name is the best alias)
    Assertions.assertEquals(1, RulesUtils.nameTokensOverlap("John Smith", wle, false));
    // Test with an exact match on the primary name
    Assertions.assertEquals(2, RulesUtils.nameTokensOverlap("Jonathan Smith", wle, false));
    // Test with an identical WLE name (reflects the best alias), no aliases, and no primary name
    wle.setPrimaryName("");
    Assertions.assertEquals(2, RulesUtils.nameTokensOverlap("John Smith", wle, false));

    wle.setPrimaryName("Jonathan Smith");

    // Test with partial matches
    Assertions.assertEquals(1, RulesUtils.nameTokensOverlap("John Doe", wle, false));

    // Test with aliases
    Assertions.assertEquals(2, RulesUtils.nameTokensOverlap("Johnny Smith", wle, true));
    Assertions.assertEquals(1, RulesUtils.nameTokensOverlap("Jon", wle, true));

    // Test with no overlap
    Assertions.assertEquals(0, RulesUtils.nameTokensOverlap("Jane Doe", wle, false));
    Assertions.assertEquals(0, RulesUtils.nameTokensOverlap("Jane Doe", wle, true));

    // Test with empty or null input
    Assertions.assertEquals(0, RulesUtils.nameTokensOverlap("", wle, false));
    Assertions.assertEquals(0, RulesUtils.nameTokensOverlap(null, wle, false));
  • float nameTokensOverlapRatio(String name, PaymentWatchListEntity wle, boolean useAliases) calculates the maximum token overlap ratio between the given name and the names or aliases associated with a payment watchlist entity object. This ratio indicates the degree of similarity based on common tokens (words) shared between the strings.

    PaymentWatchListEntity wle = new PaymentWatchListEntity()
    .setName("John Smith")
    .setPrimaryName("Jonathan Smith")
    .setAliases(Arrays.asList(
    new AliasInfo().setName("Jon Smith"),
    new AliasInfo().setName("Johnny Smith")
    ));

    // Test with an exact match on the primary name
    assertEquals(1.0, RulesUtils.nameTokensOverlapRatio("Jonathan Smith", wle, false));

    // Test with an exact match on the name (reflects the best alias)
    assertEquals(0.5, RulesUtils.nameTokensOverlapRatio("John Smith", wle, false));
    // Test with an identical WLE name (reflects the best alias), no aliases, and no primary name
    wle.setPrimaryName("");
    assertEquals(1.0, RulesUtils.nameTokensOverlapRatio("John Smith", wle, false));

    wle.setPrimaryName("Jonathan Smith");

    // Test a match via an alias
    assertEquals(1.0, RulesUtils.nameTokensOverlapRatio("Jon Smith", wle, true));
    assertEquals(0.66, RulesUtils.nameTokensOverlapRatio("Johnny", wle, true), 0.05f);

    // Test with no exact match but a partial ratio
    assertEquals(0.5, RulesUtils.nameTokensOverlapRatio("Smith John", wle, false));
    assertEquals(0.5, RulesUtils.nameTokensOverlapRatio("John S", wle, false));

    // Test with no match
    assertEquals(0.0, RulesUtils.nameTokensOverlapRatio("Jane Doe", wle, false));
  • boolean hasMatchingNameAbbreviation(String nameA, String nameB) determines whether the two name strings have matching abbreviations by comparing their unique tokens after removing the tokens they have in common. The abbreviation match is based on the sequential order of tokens.

    assertTrue(RulesUtils.hasMatchingNameAbbreviation("United Nations", "U.N."));
    assertTrue(RulesUtils.hasMatchingNameAbbreviation("International Business Machines", "IBM"));
    assertTrue(RulesUtils.hasMatchingNameAbbreviation("Federal Bureau of Investigation", "FBI"));
    assertTrue(RulesUtils.hasMatchingNameAbbreviation("Federal Bureau Investigation", "F.B.I."));
    assertTrue(RulesUtils.hasMatchingNameAbbreviation("New Yakutia Motor Corp", "New Yakutia MC Ltd"));
    assertTrue(RulesUtils.hasMatchingNameAbbreviation("Global Tech Solutions", "GTS Holdings"));
    assertTrue(RulesUtils.hasMatchingNameAbbreviation("United Nations Educational Scientific Cultural Organization", "UNESCO"));

    assertTrue(RulesUtils.hasMatchingNameAbbreviation("John Fitzgerald Kennedy", "JFK"));
    assertTrue(RulesUtils.hasMatchingNameAbbreviation("John F. Kennedy", "JFK"));
    assertTrue(RulesUtils.hasMatchingNameAbbreviation("Franklin Delano Roosevelt", "FDR"));
    assertTrue(RulesUtils.hasMatchingNameAbbreviation("Martin Luther King", "MLK"));
    assertTrue(RulesUtils.hasMatchingNameAbbreviation("John Jay Smith", "J.J.S."));
    assertTrue(RulesUtils.hasMatchingNameAbbreviation("John B Carter", "John Bill Carter"));
    assertTrue(RulesUtils.hasMatchingNameAbbreviation(" Carter, JB", "John Bill Carter"));

    assertTrue(RulesUtils.hasMatchingNameAbbreviation("National Aeronautics and Space Administration", "N.A.S.A."));
    assertTrue(RulesUtils.hasMatchingNameAbbreviation("central intelligence agency", "CIA"));
  • boolean isNamesEqual(String name1, String name2) compares two names to determine whether they are considered equal. The comparison checks whether the common token ratio between the names is 1 or whether their concatenated normalized forms are identical.

    assertTrue(RulesUtils.isNamesEqual("John Smith", "   John   Smith"));
    assertTrue(RulesUtils.isNamesEqual("Alice-Bob", "AliceBob"));
    assertTrue(RulesUtils.isNamesEqual("12345", "12 345"));

    assertFalse(RulesUtils.isNamesEqual("AliceBob", "Alice Bob Charlie"));
    assertFalse(RulesUtils.isNamesEqual("John Smith", "JohnDoe"));
    assertFalse(RulesUtils.isNamesEqual("12345", "54321"));
    assertTrue(RulesUtils.isNamesEqual("John Smith", "Smith, John"));

Name match (character-based)

  • int charsDifferenceDirectional(String one, String two) returns the minimal number of characters from string one that must be removed from (or added to) string two to make the strings equal.

    Assertions.assertEquals(0, RulesUtils.charsDifferenceDirectional("Max Johnson", "Johnson Max"));
    Assertions.assertEquals(2, RulesUtils.charsDifferenceDirectional("Maxim Johnson", "Johnson Max"));
    Assertions.assertEquals(3, RulesUtils.charsDifferenceDirectional("Max Johnson", "Xam Jonson"));
  • int charsDifference(String one, String two) returns the minimal number of characters that must be removed from (or added to) the respective strings to make them equal.

    Assertions.assertEquals(0, RulesUtils.charsDifference("Johnson", "Johnson"));
    Assertions.assertEquals(2, RulesUtils.charsDifference("Yohnvson", "Johnson"));
    Assertions.assertEquals(7, RulesUtils.charsDifference(null, "Johnson"));
    Assertions.assertEquals(7, RulesUtils.charsDifference(StringUtils.EMPTY, "Johnson"));
    Assertions.assertEquals(7, RulesUtils.charsDifference("Johnson", StringUtils.SPACE));
    Assertions.assertEquals(3, RulesUtils.charsDifference("Max Johnson", "Johnson"));
    Assertions.assertEquals(2, RulesUtils.charsDifference("Max Johnson", "Rex Johnson"));
    Assertions.assertEquals(2, RulesUtils.charsDifference("Max Johnson", "Xam Johnson"));
    Assertions.assertEquals(3, RulesUtils.charsDifference("Max Johnson", "Xam Jonson"));
    Assertions.assertEquals(4, RulesUtils.charsDifference("Maxim Johnson", "Xam Jonson"));
    Assertions.assertEquals(3, RulesUtils.charsDifference("Maxim Johnson", "Max Jongerson"));
    Assertions.assertEquals(0, RulesUtils.charsDifference("Max Johnson", "Johnson Max"));
    Assertions.assertEquals(0, RulesUtils.charsDifference("Max Gay Johnson", "Johnson, Max Gay"));
    Assertions.assertEquals(0, RulesUtils.charsDifference("Max Gay Johnson", "Johnson, Gay Max"));
    Assertions.assertEquals(0, RulesUtils.charsDifference("Max Gay Gay Johnson", "Johnson, Gay Max"));
    Assertions.assertEquals(1, RulesUtils.charsDifference("Max Gay Johnson", "Johnson, Gray Max"));
    Assertions.assertEquals(3, RulesUtils.charsDifference("Max Gay Gay Johnson", "Johnson, Gray Max"));
    Assertions.assertEquals(10, RulesUtils.charsDifference("John Gay, Robin Johnson", "John Robin"));
  • float nameCommonCharsRatio(String one, String two, int minMatchingChars) calculates the ratio of common characters between two strings, considering only matches that meet or exceed the specified minimum number of matching characters.

    Assertions.assertEquals(1.0, RulesUtils.nameCommonCharsRatio("Jonathan Smith", "Jonathan Smith", 1));
    Assertions.assertEquals(0.92, RulesUtils.nameCommonCharsRatio("Jonathan Smith", "Jonathon Smith", 1), 0.01);
    Assertions.assertEquals(1.0, RulesUtils.nameCommonCharsRatio("John Smith", "Smith John", 1));
    Assertions.assertEquals(0.6, RulesUtils.nameCommonCharsRatio("Johnson", "Jon", 1), 0.01);
    Assertions.assertEquals(0.0, RulesUtils.nameCommonCharsRatio("John", "Mike", 1));
    Assertions.assertEquals(0.0, RulesUtils.nameCommonCharsRatio("John", StringUtils.EMPTY, 1));
    Assertions.assertEquals(0.0, RulesUtils.nameCommonCharsRatio(StringUtils.EMPTY, "John", 1));
  • float nameCommonCharsDirectionalRatio(String one, String two, int minMatchingChars) calculates the directional ratio of common characters between two strings. The method determines the proportion of characters in the first string that match the second string, based on the specified minimum number of matching characters.

    Assertions.assertEquals(1.0f, RulesUtils.nameCommonCharsDirectionalRatio("Jonathan Smith", "Jonathan Smith", 1));
    Assertions.assertEquals(1.0f, RulesUtils.nameCommonCharsDirectionalRatio("Alice", "Alice", 1));
    Assertions.assertEquals(0.92f, RulesUtils.nameCommonCharsDirectionalRatio("Jonathon Smith", "Jonathan Smith", 1), 0.01f);
    Assertions.assertEquals(0.62f, RulesUtils.nameCommonCharsDirectionalRatio("Alice Bob", "Alice Charlie", 2), 0.01f);
    Assertions.assertEquals(0.0f, RulesUtils.nameCommonCharsDirectionalRatio("John", "Mike", 1));
    Assertions.assertEquals(0.53f, RulesUtils.nameCommonCharsDirectionalRatio("Jonathan Smith", "Samantha Johnson", 2), 0.01);

Date match

  • minimumDistance(String date1, String date2) calculates the minimum distance between dates represented in two given strings. Each string can contain either a single date value or multiple source dates separated by a pipe (|) delimiter.

    assertEquals(0L, RulesUtils.minimumDistance("", ""));
    assertEquals(0L, RulesUtils.minimumDistance(null, null));
    assertEquals(0L, RulesUtils.minimumDistance("34-99", "56|99"));
    assertEquals(0L, RulesUtils.minimumDistance("76", null));
    assertEquals(0L, RulesUtils.minimumDistance("76", "76|77"));
    assertEquals(0L, RulesUtils.minimumDistance("76", "1976|19"));

    assertEquals(1, RulesUtils.minimumDistance("01/01/1976", "01/02/1976"));
    assertEquals(31, RulesUtils.minimumDistance("01/01/1976", "02/01/1976"));
    assertEquals(31, RulesUtils.minimumDistance("01/01/1976", "02/1976"));
    assertEquals(31, RulesUtils.minimumDistance("01/01/1976", "1976-02-01"));
    assertEquals(31, RulesUtils.minimumDistance("01/01/1976", "1976-02-01| 1977-01-01"));
    assertEquals(365, RulesUtils.minimumDistance("01/01/1975", "1976-01-01| 1977-01-01"));
    Assertions.assertEquals(356, RulesUtils.minimumDistance("01/01/1975|01/10/1975", "1976-01-01| 1977-01-01"));

Maps

  • mapContainsValue(Map<String, String> messageMap, String key, String value) returns true if a map contains a key with the specified value.

    Assertions.assertFalse(RulesUtils.mapContainsValue(new HashMap<>() {{
    put("A", "A");
    put("B", "B");
    }}, "A", "B"));

    assertTrue(RulesUtils.mapContainsValue(new HashMap<>() {{
    put("A", "A");
    put("B", "B");
    }}, "A", "A"));
  • textContainsMapValue(String text, Map<String, String> valueMap) checks if text contains any value from the map.

    Assertions.assertFalse(RulesUtils.textContainsMapValue("000012345 john Doe 123 street",
    new HashMap<>() {{
    put("code1", "1256");
    put("code2", "00165");
    }}));

    assertTrue(RulesUtils.textContainsMapValue("000012345 john Doe 123 street",
    new HashMap<>() {{
    put("code1", "12345");
    put("code2", "00165");
    }}));

Location in text

  • isWleLocationInText(String text, PaymentWatchListEntity wle) checks if any sanctioned location elements (name, city, or country) are found in text. This is intended to be used when a sanctioned type is LOCATION.

    PaymentWatchListEntity wle1 = new PaymentWatchListEntity().setCity("Moscow|Novgorod").setCountry("RU|RUS|Russia").setName("Russian Federation");
    PaymentWatchListEntity wle2 = new PaymentWatchListEntity().setCity("Havana").setCountry("CUBA").setName("Havana");
    PaymentWatchListEntity wle3 = new PaymentWatchListEntity().setCountry("CUBA").setName("Havana");
    PaymentWatchListEntity wle4 = new PaymentWatchListEntity().setName("Russian Federation");

    //then
    assertTrue(RulesUtils.isWleLocationInText("Moscow investment bank", wle1));
    assertTrue(RulesUtils.isWleLocationInText("Russian Federation investment bank", wle1));
    assertTrue(RulesUtils.isWleLocationInText("Russian Federation investment bank", wle4));
    assertTrue(RulesUtils.isWleLocationInText("Investment Bank RU", wle4));
    assertTrue(RulesUtils.isWleLocationInText("Jack Havana", wle3));

    Assertions.assertFalse(RulesUtils.isWleLocationInText("Jack Havanan", wle2));
    Assertions.assertFalse(RulesUtils.isWleLocationInText("Jack Havanan", wle3));
  • isWleLocationInText(String text, PaymentWatchListEntity wle, boolean searchName) checks whether any sanctioned location elements (name, city, or country) are found in text. When searchName is set to false, the function excludes name-based matching, which allows it to be used for any entity type or for searching the entire content for a matching location.

    PaymentWatchListEntity wle1 = new PaymentWatchListEntity().setCity("Moscow|Novgorod").setCountry("RU|RUS|Russia").setName("Russian Federation");
    PaymentWatchListEntity wle2 = new PaymentWatchListEntity().setCity("Havana").setCountry("CUBA").setName("Havana");
    PaymentWatchListEntity wle3 = new PaymentWatchListEntity().setCountry("CUBA").setName("Havana");
    PaymentWatchListEntity wle4 = new PaymentWatchListEntity().setName("Russian Federation");

    assertTrue(RulesUtils.isWleLocationInText("Moscow investment bank", wle1, true));
    assertTrue(RulesUtils.isWleLocationInText("Russian Federation investment bank", wle1, true));
    assertTrue(RulesUtils.isWleLocationInText("Russian Federation investment bank", wle1, false));
    assertTrue(RulesUtils.isWleLocationInText("Russian Federation investment bank", wle4, false));
    assertTrue(RulesUtils.isWleLocationInText("Russia investment bank", wle1, true));
    assertTrue(RulesUtils.isWleLocationInText("RUS investment bank", wle1, true));
    assertTrue(RulesUtils.isWleLocationInText("RU investment bank", wle1, true));
    assertTrue(RulesUtils.isWleLocationInText("Novgorod Nafta Invest ", wle1, true));
    assertTrue(RulesUtils.isWleLocationInText("BRD TD ACCT CU", wle2, true));
    assertTrue(RulesUtils.isWleLocationInText("BRD TD CUBAN ACCT", wle2, true));
    assertTrue(RulesUtils.isWleLocationInText("BRD TD ACCT CUB", wle2, true));
    assertTrue(RulesUtils.isWleLocationInText("Beautiful Havana City", wle2, true));

    Assertions.assertFalse(RulesUtils.isWleLocationInText("RusOilAndGus company", wle1, true));
    Assertions.assertFalse(RulesUtils.isWleLocationInText("These rumors were not true ", wle1, true));
    Assertions.assertFalse(RulesUtils.isWleLocationInText("Belarusbank", wle1, true));
    Assertions.assertFalse(RulesUtils.isWleLocationInText("BRD TD SCUBA ACCT", wle2, true));
    Assertions.assertFalse(RulesUtils.isWleLocationInText("Beautiful Havana City", wle3, false));
  • isLocationAnyMatch(String scrCountry, String wleCountry) handles countries that can contain multiple pipe-separated values like RU|GB.

    Assertions.assertFalse(RulesUtils.isLocationAnyMatch("", ""));
    Assertions.assertFalse(RulesUtils.isLocationAnyMatch(null, null));
    Assertions.assertFalse(RulesUtils.isLocationAnyMatch("cuba|iran|russia|TR|USA", null));
    Assertions.assertFalse(RulesUtils.isLocationAnyMatch("", "cuba|iran|russia|TR|USA"));
    Assertions.assertFalse(RulesUtils.isLocationAnyMatch("cuba|iran|russia|TR", "GB|USA"));
    Assertions.assertFalse(RulesUtils.isLocationAnyMatch("russia", "Russian Federation"));

    assertTrue(RulesUtils.isLocationAnyMatch("cuba|USA|russia|TR", "GB|USA"));
  • isContentIdMatchesWleCountry(String content, String format, String type, String tags, String wleCountry) searches the message content for known IDs within defined tags and checks for matches with sanctioned countries.

    String content = "{1:F01CRASGB2LAXXX0000000000}{2:O1030024250909BTAMBSNSAXXX00000000002509090024N}\n" +
    ":52A:BTAMBSNS\n:57A:HSBCHKHHHKH\n" +
    ":71A:OUR\n:77B:/ORDERRES/BS/BENEFRES/HK\n-}";
    String format = "NATIVE";
    String type = "SWF";
    String tags = "57A,52A";

    assertTrue(RulesUtils.isContentIdMatchesWleCountry(content, format, type, tags, "BS"));
    assertTrue(RulesUtils.isContentIdMatchesWleCountry(content, format, type, tags, "HK"));

    assertFalse(RulesUtils.isContentIdMatchesWleCountry(content, format, type, tags, "RU"));
  • getCountriesFromText(String text, boolean detectIso2Codes, boolean detectIso3Codes) searches a given text (location) for possible country references and returns a string of pipe-separated ISO2 codes for the countries found (for example, US|GB). It performs a string-based search and cannot determine whether a token represents an actual country or is part of a person's name or a street name.

    Assertions.assertEquals("", RulesUtils.getCountriesFromText("", true, true));
    Assertions.assertEquals("US", RulesUtils.getCountriesFromText("This is a payment from United States.", true, true));
    Assertions.assertEquals("CA|DE|US", RulesUtils.getCountriesFromText("This includes Canada, United States, and also Germany.", true, true));
    Assertions.assertEquals("GB|US", RulesUtils.getCountriesFromText("The transaction involves US and GB.", true, true));
    Assertions.assertEquals("US", RulesUtils.getCountriesFromText("This includes US and GBR.", true, false));
    Assertions.assertEquals("", RulesUtils.getCountriesFromText("There is no valid country here.", true, true));
    Assertions.assertEquals("DE|JP", RulesUtils.getCountriesFromText("Transactions from GERmany and jaPan!", true, true));
  • Boolean isTextCountryMatchesWleCountry(String text, boolean detectIso2Codes, boolean detectIso3Codes, String wleCountry) checks whether a sanctioned country matches any country found in the text and returns null if the text does not contain countries; otherwise, returns true or false.

    assertNull(isTextCountryMatchesWleCountry("", true, true, "RU"));
    assertEquals(Boolean.TRUE, isTextCountryMatchesWleCountry("Payment from Germany via USA to Canada", true, true, "CA"));
    assertEquals(Boolean.FALSE, isTextCountryMatchesWleCountry("Payment from Germany via USA to Canada", true, true, "RU|IR"));
    assertNull(isTextCountryMatchesWleCountry("This payment is in progress with no issues for us", true, true, "RU"));

Support comparison against CSV file

The functions defined in CsvCacheHelper allow values to be compared against CSV data downloaded from the S3-compatible object storage in the doc-upload bucket under the payment_sanctions_screening/rules/data directory. These functions support multiple action types to compare strings, numbers, dates, and regular expressions. See the CsvMatchConditionTest.java file for examples.

// String matches
EQUALS,NOT_EQUALS,CONTAINS,NOT_CONTAINS,STARTS_WITH,ENDS_WITH,
FIRST_N_CHARS,LAST_N_CHARS,IS_EMPTY,IS_NOT_EMPTY,IN_LIST,
// Regex
REGEX_MATCH,REGEX_NOT_MATCH,REGEX_CONTAINS,REGEX_NOT_CONTAINS,
// Numeric
EQUALS_NUMERIC,NOT_EQUALS_NUMERIC,GREATER_THAN,LESS_THAN,BETWEEN,
// Date
DATE_EQUALS,DATE_NOT_EQUALS,DATE_BEFORE,DATE_AFTER,DATE_BETWEEN,DATE_WITHIN_DAYS
  • matchExists(String csvFileName, String factor, Type actionType, String columnName) compares the parameter to each row in the specified CSV column using the provided comparison type.

    // Sample rule for matching data factors against a set of values defined in a CSV file
    // Simplified version of rowMatches() for matching a factor against a large list of values
    rule "Example matchExists"
    activation-group "default"
    salience 1498
    when
    $hit: PaymentMessageHit()
    eval(CsvCacheHelper.matchExists("company_entries.csv", $hit.hitText, CsvMatchCondition.Type.CONTAINS, "Reference Label"))
    then
    RulesUtils.resolve("Hit matches company_entries.csv", rulesDecision);
    end
  • rowMatches(String csvFileName, List<CsvMatchCondition> conditions, MatchMode matchMode) compares the list of conditions to each row in the CSV file. For MatchMode.ALL, all conditions must match; for MatchMode.ANY, any condition can trigger a match.

    // Sample rule for matching data factors against a set of values defined in a CSV file
    // The company_account_entries.csv file should be placed in the payment_sanctions_screening/rules/data/ S3 directory
    // All conditions must match within the same CSV row
    rule "CSV data match example"
    activation-group "default"
    salience 1499
    when
    $hit: PaymentMessageHit()
    $msg: PaymentMessage()
    eval($msg.payment != null && $msg.payment.sender != null)
    eval(CsvCacheHelper.rowMatches(
    "company_account_entries.csv",
    Arrays.asList(
    new CsvMatchCondition($hit.hitText, CsvMatchCondition.Type.CONTAINS, "Reference Label"),
    new CsvMatchCondition($msg.payment.sender.name, CsvMatchCondition.Type.CONTAINS, "Company Name"),
    new CsvMatchCondition($msg.payment.sender.accountNumber, CsvMatchCondition.Type.ENDS_WITH, "Account Number")
    ),
    CsvMatchCondition.MatchMode.ALL
    ))
    then
    RulesUtils.resolve("CSV data match example", rulesDecision);
    end