> ## Documentation Index
> Fetch the complete documentation index at: https://docs.frankieone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Managing Duplicate Entities

A detailed guide to detecting, reviewing, and resolving duplicate customer records using FrankieOne's automated workflow step.

## What are Duplicate Checks?

Duplicate checks are essential for maintaining data integrity, reducing fraud risk, and ensuring compliance. They prevent the creation of multiple records for the same individual within your system.

FrankieOne integrates this capability directly into your KYC workflow via a dedicated **`DUPLICATE`** step. This step provides:

* **Automated identification** of possible duplicates using configurable, rules-based matching logic.
* **Manual review and resolution** capabilities for operators to confirm or dismiss flagged duplicates.
* **Seamless integration** with existing KYC processes to support both operational efficiency and regulatory compliance.

<Callout icon="star" color="#3DD892" iconType="regular">
  To implement duplicate checks, you must have the `DUPLICATE` step added to your KYC workflow. **Speak with your FrankieOne representative to configure the duplicate step and its matching rules.**
</Callout>

***

## How Duplicate Checks Work

The process is designed to flag potential duplicates during onboarding and provide your team with the tools to resolve them.

<Steps>
  <Step title="1. Workflow Execution">
    When you execute a workflow for a new or updated entity, the `DUPLICATE` step runs. It compares the entity's attributes (e.g., name, DOB, document number) against all other entities in your system based on your configured matching rules.
  </Step>

  <Step title="2. A 'HIT' is Returned">
    If a potential duplicate is found, the workflow step returns a `result` of `HIT`, and a `DUPLICATE` issue is created. This typically causes the overall workflow `status` to become `REVIEW`.
  </Step>

  <Step title="3. Detailed Results are Generated">
    For each potential duplicate found, a Process Result Object (PRO) is created. This PRO contains the granular details of the match, including which fields matched and the `entityId` of the potential duplicate.
  </Step>

  <Step title="4. Operator Review and Resolution">
    Your compliance team reviews the evidence within the PROs and makes a decision. They then resolve the match by calling the API to update the PRO's `manualStatus`.
  </Step>

  <Step title="5. State and Relationships are Updated">
    Based on the operator's resolution, the system updates the service profile states (e.g., marking one entity as `DUPLICATE`) and creates a permanent `relationship` between the two entities for future reference.
  </Step>
</Steps>

***

## Implementation and API Guide

### Step 1: Interpreting the Duplicate Check Result

When the `DUPLICATE` step returns a `HIT`, you need to parse the `processResults` within that step to understand the match. Each PRO will have `class: "DUPLICATE"`.

The most important information is in the **`supplementaryData`** object.

<Accordion title="Example: supplementaryData for a Duplicate PRO">
  ```json theme={null}
  {
      "processResultId": "01JNFWT5MKE75FMMZPWC7SDG2Z",
      "result": "HIT",
      "class": "DUPLICATE",
      "stepName": "DUPLICATE",
      "supplementaryData": {
          "type": "DUPLICATE",
          "duplicateEntityId": "98c545b6-7545-4337-85ce-30c64ef1ccff",
          "matchedFields": [
              {
                  "objectType": "NAME",
                  "objectId": "840268d6-d37e-43ad-a421-d35345472df4",
                  "duplicateObjectId": "4331b636-5cb2-46d6-bba1-03536232909b",
                  "matchStrength": 86
              },
              {
                  "objectType": "ADDRESS",
                  "objectId": "9c5fdaeb-056a-47c7-a36e-dcb59ddce5b3",
                  "duplicateObjectId": "c0d6872f-dd14-43f2-86b2-60e1e7ae24c4",
                  "matchStrength": 100
              }
          ],
          "matchedRules": [
              {
                  "name": "Given + Family name + Short form normalised address",
                  "strength": "HIGH"
              }
          ]
      }
  }
  ```
</Accordion>

**Key `supplementaryData` Fields:**

* `duplicateEntityId`: The `entityId` of the existing profile that was matched.
* `matchedFields`: An array detailing exactly which attributes matched between the two entities (e.g., `NAME`, `ADDRESS`). It includes the specific `objectId` for each attribute and the `matchStrength`.
* `matchedRules`: Shows which configured rule was triggered to create this match.

### Step 2: Resolving Duplicates

After reviewing the evidence, an operator must classify the match. This is done by sending a `PATCH` request with the `processResultId` and a `manualStatus`.

**Endpoint:**

```http theme={null}
    PATCH /v2/individuals/{entityId}/results/duplicate
```

**Understanding the Manual Statuses**
The `manualStatus` you send should reflect the operator's decision.

| Operator's Decision                                                                   | `manualStatus` to Send | Outcome                                                                               |
| :------------------------------------------------------------------------------------ | :--------------------- | :------------------------------------------------------------------------------------ |
| "This is **not a duplicate**."                                                        | `FALSE_POSITIVE`       | The potential match is dismissed. No relationship is created.                         |
| "This is a duplicate. **Keep the new entity** and mark the old one as the duplicate." | `TRUE_POSITIVE_ACCEPT` | A relationship is created. The old entity's service profile is marked as `DUPLICATE`. |
| "This is a duplicate. **Keep the old entity** and mark the new one as the duplicate." | `TRUE_POSITIVE_REJECT` | A relationship is created. The new entity's service profile is marked as `DUPLICATE`. |

<Accordion title="Example: Resolve a Duplicate as False Positive">
  ```bash theme={null}
      curl -X PATCH \
      '{{baseHost}}/v2/individuals/{{entityId}}/results/duplicate' \
      -H 'api_key: YOUR_API_KEY' \
      -H 'X-Frankie-CustomerID: YOUR_CUSTOMER_ID' \
      -d '{
          "processResults": [
              "01JNFWT5MKE75FMMZPWC7SDG2Z"
          ],
          "manualStatus": "FALSE_POSITIVE",
          "comment": {
              "text": "Reviewed by operator. Confirmed different individuals with similar names."
          }
      }'
  ```
</Accordion>

<Callout icon="thumbtack" color="#1A6CFF" iconType="regular">
  After resolving all duplicate-related `issues`, you should **re-run the workflow**. The `DUPLICATE` step will re-evaluate based on your resolutions, and the overall workflow `status` can then update from `REVIEW` to `PASS`.
</Callout>

***

## Key Concepts: State and Relationships

* **Relationships**: When a duplicate is confirmed (`TRUE_POSITIVE_ACCEPT` or `TRUE_POSITIVE_REJECT`), a permanent `relationship` is created between the two entities. You can see this in the response of `GET /v2/individuals/{entityId}` under the `relationships.duplicates` array.
* **`DUPLICATE` State**: An entity's service profile is moved to the `DUPLICATE` state when a relationship identifies it as such. An entity remains a duplicate as long as at least one relationship points to it. If all such relationships are removed (e.g., by being marked as `FALSE_POSITIVE` or if the source entity is deleted), its `DUPLICATE` state will be revoked.

***

## Configuration (Reference)

The matching logic for the `DUPLICATE` step is highly configurable by the FrankieOne team.

<Accordion title="Example duplicate.json Matching Rules">
  ```json theme={null}
  {
      "ruleSets": {
          "default": {
              "rules": [
                  {
                      "name": "Document identifiers",
                      "matchFields": [ "DOC_PRIMARY_IDENTIFIER", "DOC_TYPE", "DOC_COUNTRY" ],
                      "method": "EXACT",
                      "riskFactor": "VERY_HIGH"
                  },
                  {
                      "name": "Given + Family name + Date of birth",
                      "matchFields": [ "GIVEN_NAME", "FAMILY_NAME", "DATE_OF_BIRTH" ],
                      "method": "EXACT",
                      "riskFactor": "VERY_HIGH"
                  }
              ]
          }
      }
  }
  ```
</Accordion>

### Duplicate Check Logic

FrankieOne's duplicate detection uses configurable rules to compare key attributes between entities. If any rule is matched, the entity is flagged as a potential duplicate.

**Matching Rule Examples:**

* **External Reference:** Exact match on `EXTERNAL_REFERENCE`.
* **Document Identifiers:** Exact match on all of `DOC_PRIMARY_IDENTIFIER`, `DOC_SECONDARY_IDENTIFIER`, `DOC_COUNTRY`, `DOC_SUBDIVISION`, and `DOC_TYPE`.
* **Phone Number:** Exact match on `PHONE_NUMBER`.
* **Email Address:** Exact match on `EMAIL_ADDRESS`.
* **Full Name:** Exact match on both `GIVEN_NAME` and `FAMILY_NAME`.
* **Full Name + Date of Birth:** Exact match on `GIVEN_NAME`, `FAMILY_NAME`, and `DATE_OF_BIRTH`.
* **Full Name + Normalised Address:** Exact match on `ADDR_NORM_SHORT`, `GIVEN_NAME`, and `FAMILY_NAME`.

All rules use exact matching. You can customise which rules are active and their risk levels by working with FrankieOne.
