Harmoney Data Due Diligence Provider
- Version 3
- Version 2
π― Functional Documentationβ
The V3 Data Due Diligence Provider represents a major architectural evolution in how due diligence parameters are evaluated. Unlike its predecessor, which treated all data points generically, V3 introduces a Rule-Based Evaluation.
- Simple Value Rules: For direct 1:1 mapping of values (e.g., Boolean to Yes/No).
- List Lookup Rules: For checking values against external risk lists (e.g., Sanctions, Embargoes).
- Advanced Logic Rules: For complex, multi-field conditions (e.g., "If Company Age < 2 AND Sector = 'Crypto'").
βοΈ Configurationβ
- Implementation Class:
eu.myharmoney.app.providers.duediligence.HarmoneyV3DataDueDiligenceProvider - Key Configuration Field:
parameterConfig
This provider does not support due diligence document generation, so the configuration parameter DUE_DILIGENCE_DOCUMENT_FETCH will be of no use here.
The configuration is no longer a flat list of fields. Instead, each parameter is defined by the specific type of rule it employs.
The Three Rule Typesβ
1. Simple Value Rules (simpleValueRules)β
Best for direct 1:1 transformations of system data (like Booleans or Enums) into RBA results.
2. Advanced Logic Rules (advancedLogicRules)β
The powerhouse of the provider. Use this when a single field isn't enough. It supports nested boolean logic (AND, OR) and various operators (greaterThan, equals, in).
3. List Lookup Rules (listLookupRules)β
Dedicated to checking values against external or internal risk lists (e.g., Sanctions or High-Risk Countries).
π Configuration Examplesβ
Settings Configuration
Examples
The Three Safety Nets (Fallback Logic)β
:::warning Important migration behavior
In V3, resultForNullAnswer defaults to UNDEFINED.
In V2, the effective default behavior was usually "no result for missing answer" (commonly represented by leaving resultForNullAnswer as null in mappings).
This means a V2 configuration migrated to V3 without explicitly setting resultForNullAnswer can start returning UNDEFINED for missing answers, which can change final RBA outcomes.
If you need V2 parity for "missing answer", set resultForNullAnswer: null explicitly per rule.
:::
1. resultForNullAnswerβ
Trigger Scenario:
Data is Missing β Triggered when the system finds no data (null) for the configured fieldPath.
Functional Purpose: Addresses Missing Information. In compliance workflows, "Missing" should usually remain OPEN for manual review rather than defaulting to "No Hit".
Recommended Default:
UNDEFINED
For V2 parity (optional):
Use null explicitly when you want "no result" for missing answers.
2. resultForUnmappedAnswerβ
Trigger Scenario:
Logic is False β Triggered when data exists (e.g., "false" or "Option C") but does not match any configured valueMappings or logical conditions.
Functional Purpose: Acts as the "Else" statement. It captures unexpected or "safe" values that weren't explicitly mapped in the rule.
Recommended Default:
UNDEFINED
3. resultMappedAnswerResultUnknownβ
Trigger Scenario: Data Integrity Error (List Rules Only) β Triggered if a value is found in a list, but that specific list entry has an empty or invalid result.
Functional Purpose: Acts as a System Fail-safe. It flags list corruption or incomplete database entries to the auditor via a high-score alert.
Recommended Default:
UNDEFINED
β€΄οΈ Migration Guide: V2 to V3β
The V3 provider moves from a Field-Centric approach to a Rule-Centric approach. You cannot simply copy-paste V2 configurations; you must categorize them into the correct rule buckets.
β οΈ Additional Required Migration Check: resultForNullAnswerβ
Besides moving rules to the correct V3 buckets, you must verify null-answer behavior for every rule:
- V3 default:
resultForNullAnswer = UNDEFINED - Common V2 behavior: missing answer often produced no parameter result
If your V2 behavior relied on "no result for missing answer", set:
"resultForNullAnswer": null
on each migrated V3 rule where that behavior is required.
Key Changes
-
Explicit Categorization: You no longer dump everything into a generic
fieldslist. You must place rules intosimpleValueRules,listLookupRules, oradvancedLogicRules. -
No More "Hidden" Logic: In V2, complex logic was hidden inside a
directMappingof a generic field. In V3, this explicitly belongs inadvancedLogicRules.
β‘ Migration Cheat Sheet Compare how a configuration transforms from V2 to V3 below.
- Simple Value Migration
Goal: Map question_pep (true/false) to YES/NO.
β V2 (Old) Generic fields list with implicit simple logic.
"fields": [
{
"fieldPath": "individual.questionAnswers.question_pep",
"mapping": {
"directMapping": [
{ "answer": true, "parameterResult": "YES" }
]
}
}
]
β V3 (New) simpleValueRules
"simpleValueRules": [
{
"fieldPath": "individual.questionAnswers.question_pep",
"valueMappings": [
{ "answer": true, "parameterResult": "YES" }
],
"resultForNullAnswer": null, // Add for V2 parity when missing answers should return no result
"resultForUnmappedAnswer": "NO" // Explicit fallback for 'false'
}
]
- List Lookup Migration
Goal: Check Country Code against a specific risk list.
β V2 (Old) Generic field with listIdentifier buried in mapping.
"fields": [
{
"fieldPath": "individual.address.countryCode",
"mapping": {
"listIdentifier": "country_risk_check",
"resultForUnmappedAnswer": "LOW"
}
}
]
β V3 (New) Dedicated listLookupRules list.
"listLookupRules": [
{
"fieldPath": "individual.address.countryCode",
"listIdentifier": "country_risk_check",
"resultForNullAnswer": null, // Add for V2 parity when missing answers should return no result
"resultForUnmappedAnswer": "LOW"
}
]
- Advanced Logic Migration
Goal: Check if a number is within a specific range & client_self_employed field is true
β V2 (Old) Not possible to evaluate using multiple field paths
"fields": [
{
"fieldPath": "company.companyNumber",
"mapping": {
"directMapping": [
{
"conditionalAnswer": {
"greaterThan": { "value": 1000 }
},
"parameterResult": "HIGH"
}
]
}
}
]
β V3 (New) Explicit advancedLogicRules structure, with support to multiple field paths
"advancedLogicRules": [
{
"description": "Check company number range",
"resultForNullAnswer": null, // Add for V2 parity when missing answers should return no result
"mapping": {
"directMapping": [
{
"parameterResult": "HIGH",
"conditionalAnswer": {
"and": [
{ "smallerThan": { "fieldPath": "company.companyNumber", "value": 9999999999 } },
{ "greaterThan": { "fieldPath": "company.companyNumber", "value": 1000 } },
{ "equals": { "fieldPath": "company.metadata.CLIENT_SELF_EMPLOYED", "value": true } }
]
}
}
]
}
}
]
π― Functional documentationβ
This provider can check data points from all over the application and map them to a DD parameter result based on the configuration.
This provider can provide due diligence results for a dossier and for each party type.
βοΈ Configurationβ
-
Implementation class:
eu.myharmoney.app.providers.duediligence.HarmoneyV2DataDueDiligenceProvider -
The provider is configured mainly by the field
parameterConfigwhich contains configurations for each due diligence parameter that is specified. See Settings schema for more information.
Mapping the data point answer to Due Diligence resultsβ
- There are 3 different ways of mapping an answer to a DD result
- The answer corresponds with the due diligence result key
- Can be used when the answer is a KEY (chosen from a list) and this is a direct match with the DD parameter result KEYβs defined in the module settings.
- The answer is begin searched in a list
- Can be used when the answer is e.g. an autocomplete value where the KEY is used to check against a predefined βriskβ list.
- The answer is mapped using the configuration
- This can be a straightforward mapping where a list of answers correspond to a certain due diligence result.
- But also possible to define using a conditional answer if there is more complex logic involved. (using a condition)
Examplesβ
(βΉοΈ) When a data point has multiple answers, each answer is mapped individually (and could lead to a multiple DD parameter results
{
"parameterConfig": {
"HIGH_RISK_COUNTRY": {
"fields": [
{
"fieldPath": "individual.address.countryCode",
"subjectTypes": ["INDIVIDUAL"],
"mapping": {
"listIdentifier": "country_risk_check",
"resultForUnmappedAnswer": "LOW",
"resultMappedAnswerResultUnknown": "HIGH"
}
},
{
"fieldPath": "company.address.countryCode",
"subjectTypes": ["COMPANY"],
"mapping": {
"listIdentifier": "country_risk_check",
"resultForUnmappedAnswer": "LOW",
"resultMappedAnswerResultUnknown": "HIGH"
}
},
{
"fieldPath": "individual.countryOfBirth",
"subjectTypes": ["INDIVIDUAL"],
"mapping": {
"listIdentifier": "country_risk_check",
"resultForUnmappedAnswer": "LOW",
"resultMappedAnswerResultUnknown": "HIGH"
}
},
{
"fieldPath": "individual.nationality",
"subjectTypes": ["INDIVIDUAL"],
"mapping": {
"listIdentifier": "country_risk_check",
"resultForUnmappedAnswer": "LOW",
"resultMappedAnswerResultUnknown": "HIGH"
}
}
]
}
}
}
{
"parameterConfig": {
"PEP": {
"fields": [
{
"fieldPath": "individual.questionAnswers.question_pep",
"subjectTypes": ["INDIVIDUAL"],
"mapping": {
"directMapping" : [
{
"answer": true,
"parameterResult": "YES"
},
{
"answer": false,
"parameterResult": "NO"
}
]
}
}
]
}
}
}
{
"parameterConfig": {
"DD_PARAM": {
"fields": [
{
"subjectTypes": ["DOSSIER"],
"fieldPath": "dossier.questionAnswers.question_number",
"mapping": {
"directMapping": [
{
"conditionalAnswer": {
"and": [
{
"greaterThan": {
"value": 99
}
},
{
"smallerThan": {
"value": 200
}
}
]
},
"parameterResult": "HIGH"
}
]
}
}
]
}
}
}
Configuration schemaβ
The most important settings are present at the bottom of the schema.
β€΄οΈ Migration guide from V1 to V2β
To migrate V1 configuration to V2, you must define the correct fieldPath.
This is not a simple 1:1 mapping, so you must think about each example individually.
- Questions are now mapped to
fieldswith afieldPaththat defines both the 'source' and thequestionIdentifier. - Fields are more or less the same, but the
fieldPathneeds to be adapted do the new format (that includes the 'source', such asdossier,individual, ... and must match the subject type).
Exampleβ
Transform
"questions": [ { "source": "QUESTION", "questionIdentifier": "some_id", ... } ]
To
"fields": [ { "fieldPath": "dossier.questionAnswers.some_id", ... } ]