Skip to main content
Version: 3.4.2

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.

  • String prepareValue(String value) returns text in uppercase, with trimmed spaces and no punctuation. Punctuation marks are replaced with spaces.

String manipulation

  • 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"));
  • 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.

  • hasMultipleTokens(String value) returns true if a string has multiple tokens separated by spaces or punctuation.

  • hasNTokens(String value, int n) returns true if a string is more than N tokens separated by spaces or punctuation.

  • 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));
  • 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"));
  • 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));
  • isNameAliasInText(String text, List<AliasInfo> aliases) checks if any alias name matches the text, using the textContainsValue function for comparison.

  • textMatchRegex(String text, String regex, boolean fullMatch) matches or finds a RegEx pattern in the given text, 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 FREE_FORMAT line)
    assertTrue(RulesUtils.textMatchRegex(text, "\\[TRANSACT\\s+\\]\\sFREE_FORMAT", false));
    //Match case-insensitive
    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));

Dates

  • minimumDistance(String date1, String date2) calculates the distance between two dates.

    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"));

Maps

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

  • textContainsMapValue(String text, Map<String, String> valueMap) checks if text contains any value from the map.

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.

  • isWleLocationInText(String text, PaymentWatchListEntity wle, boolean searchName) checks if any sanctioned location elements (name, city, or country) are found in text. When boolean searchName is set to false, it excludes name searching, allowing this function to be used for any entity type or for searching the entire content for matching locations.

  • isLocationAnyMatch(String scrCountry, String wleCountry) handles countries that can contain multiple pipe-separated values like RU|GB.

  • 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"));

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 to match data factors against set of values defined in CSV file
    // Simplified version of rowMatches() to match factor against big 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 to match data factors against set of values defined in CSV file
    // "company_account_entries.csv" file should be placed under payment_sanctions_screening/rules/data/ S3 directory
    // All conditions should 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