From 15043f5b6ab3abdb5102f80a7c4fccfd77d22c90 Mon Sep 17 00:00:00 2001 From: Sonal Date: Wed, 24 Jun 2026 20:46:04 +0530 Subject: [PATCH 001/144] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1f540cf6b..8959a4f7f 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,15 @@ With Zingg, the analytics engineer and the data scientist can quickly integrate ## Why Zingg -Zingg is an ML based tool for master data management and entity resolution. The following features set Zingg apart from other tools and libraries: -- [Interactive training data builder](https://docs.zingg.ai/latest/stepbystep/createtrainingdata/label) using active learning that builds models on frugally small training samples to high accuracy. This overcomes the challenges in techniques like [Fellegi Sunter](https://www.zingg.ai/post/fellegi-sunter-model-limitations-modern-entity-resolution) and [Entity Centric Matching](https://www.zingg.ai/post/limits-of-entity-centric-matching) +Zingg is an ML based tool for master data management and entity resolution. Zingg's ML is purpose built for entity resolution to overcome the challenges of [Fellegi Sunter](https://www.zingg.ai/post/fellegi-sunter-model-limitations-modern-entity-resolution) and [Entity Centric Matching](https://www.zingg.ai/post/limits-of-entity-centric-matching). The following features set Zingg apart from other tools and libraries: + +- [Interactive training data builder](https://docs.zingg.ai/latest/stepbystep/createtrainingdata/label) using active learning that builds models on frugally small training samples to high accuracy. ![Shows records and asks user to mark yes, no, cant say on the cli.](/assets/labelvertical.gif) +- Auto learning blocking model to scale entity resolution to millions of records. [See why this is important](https://docs.zingg.ai/latest/zmodels/) and [Zingg performance numbers](https://docs.zingg.ai/zingg/stepbystep/hardwaresizing) - Ability to handle any entity like customer, patient, supplier, product etc - Ability to connect to [disparate data sources](https://docs.zingg.ai/latest/connectors). Local and cloud file systems in any format, enterprise applications and relational, NoSQL and cloud databases and warehouses -- Ability to scale to large volumes of data. [See why this is important](https://docs.zingg.ai/latest/zmodels/) and [Zingg performance numbers](https://docs.zingg.ai/zingg/stepbystep/hardwaresizing) - Ability to define domain specific functions to improve matching - Out of the box support for English as well as Chinese, Thai, Japanese, Hindi and other languages From 7f8ab65fa6cddf7511cac6961c2c28e6fc9e0a54 Mon Sep 17 00:00:00 2001 From: Aditya Pareek <119134401+LOGANBLUE1@users.noreply.github.com> Date: Sat, 27 Jun 2026 16:16:59 +0530 Subject: [PATCH 002/144] aded proper error propagation (#1329) --- .../common/client/arguments/loader/FileArgumentLoader.java | 2 +- .../src/main/java/zingg/common/client/util/JsonStringify.java | 4 +--- .../java/zingg/common/core/preprocess/IPreprocessors.java | 2 +- .../zingg/common/core/preprocess/MultiFieldPreprocessor.java | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/common/client/src/main/java/zingg/common/client/arguments/loader/FileArgumentLoader.java b/common/client/src/main/java/zingg/common/client/arguments/loader/FileArgumentLoader.java index 86f1bf6a6..c711ea560 100644 --- a/common/client/src/main/java/zingg/common/client/arguments/loader/FileArgumentLoader.java +++ b/common/client/src/main/java/zingg/common/client/arguments/loader/FileArgumentLoader.java @@ -16,7 +16,7 @@ public A load(String filePath) throws ZinggClientException { try { return objectMapper.readValue(new File(filePath), argsClass); } catch (Exception exception) { - throw new ZinggClientException("Error reading config from file: " + filePath); + throw new ZinggClientException("Error reading config from file: " + filePath, exception); } } } diff --git a/common/client/src/main/java/zingg/common/client/util/JsonStringify.java b/common/client/src/main/java/zingg/common/client/util/JsonStringify.java index 01d817dad..af18b2c80 100644 --- a/common/client/src/main/java/zingg/common/client/util/JsonStringify.java +++ b/common/client/src/main/java/zingg/common/client/util/JsonStringify.java @@ -15,9 +15,7 @@ public static String toString (Object o){ StringWriter writer = new StringWriter(); return mapper.writeValueAsString(o); } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return null; + throw new RuntimeException("Error converting object to JSON string", e); } } diff --git a/common/core/src/main/java/zingg/common/core/preprocess/IPreprocessors.java b/common/core/src/main/java/zingg/common/core/preprocess/IPreprocessors.java index a5fe685da..558f7df38 100644 --- a/common/core/src/main/java/zingg/common/core/preprocess/IPreprocessors.java +++ b/common/core/src/main/java/zingg/common/core/preprocess/IPreprocessors.java @@ -43,7 +43,7 @@ default ZFrame preprocess(ZFrame df) throws ZinggClientException { } } catch (Exception exception) { LOG.warn("Error occurred while performing preprocessor" + exception); - throw new ZinggException("Error occurred while performing preprocessor"); + throw new ZinggException("Error occurred while performing preprocessor", exception); } return dfp; } diff --git a/common/core/src/main/java/zingg/common/core/preprocess/MultiFieldPreprocessor.java b/common/core/src/main/java/zingg/common/core/preprocess/MultiFieldPreprocessor.java index dbba6f35f..7d408e279 100644 --- a/common/core/src/main/java/zingg/common/core/preprocess/MultiFieldPreprocessor.java +++ b/common/core/src/main/java/zingg/common/core/preprocess/MultiFieldPreprocessor.java @@ -60,7 +60,7 @@ public ZFrame preprocess(ZFrame df) { } } catch (Exception exception) { LOG.warn("Error occurred while performing multifield preprocessor" + exception); - throw new ZinggException("Error occurred while performing multifield preprocessor"); + throw new ZinggException("Error occurred while performing multifield preprocessor", exception); } return df; } From 38869506f7e655bcee40ea9e754a0bf9c04f558b Mon Sep 17 00:00:00 2001 From: Chandan Date: Mon, 29 Jun 2026 15:12:00 +0530 Subject: [PATCH 003/144] performance reports with charting Co-authored-by: chandan --- .github/workflows/build.yml | 4 ++-- .github/workflows/perfTest-febrl120K.yml | 6 +++--- .github/workflows/perfTest-ncVoters5M.yml | 6 +++--- .github/workflows/pmd.yml | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c020a3186..4e55895e5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,9 +15,9 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '11' distribution: 'adopt' diff --git a/.github/workflows/perfTest-febrl120K.yml b/.github/workflows/perfTest-febrl120K.yml index c7e9dceb0..2b0744ff6 100644 --- a/.github/workflows/perfTest-febrl120K.yml +++ b/.github/workflows/perfTest-febrl120K.yml @@ -15,7 +15,7 @@ jobs: INPUT: "zingg_community_performance_reports/perf_test/perfTestInput_febrl120K.json" steps: - name: checkout repo content - uses: actions/checkout@v3 # checkout the repository content to github runner. + uses: actions/checkout@v4 # checkout the repository content to github runner. - name: Load Spark config id: cfg run: | @@ -25,7 +25,7 @@ jobs: echo "hadoop_version=$HADOOP_VERSION" >> "$GITHUB_OUTPUT" echo "spark_url=$SPARK_URL" >> "$GITHUB_OUTPUT" - name: setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.12.7 #install the python needed - uses: actions/setup-java@v4 @@ -63,7 +63,7 @@ jobs: git remote set-url origin https://x-access-token:${{ secrets.GH_PERF_COMMUNITY_ACTION }}@github.com/zinggAI/zingg_community_performance_reports.git git config user.name sonalgoyal git config user.email sonalgoyal4@gmail.com - git add perf_test/perf_test_report/*.json + git add perf_test/perf_test_report/*.json perf_test/perf_test_report/*.csv if git diff --cached --quiet; then echo "No changes to commit for perfTest-febrl120K." else diff --git a/.github/workflows/perfTest-ncVoters5M.yml b/.github/workflows/perfTest-ncVoters5M.yml index 2b589c711..454a7ddcf 100644 --- a/.github/workflows/perfTest-ncVoters5M.yml +++ b/.github/workflows/perfTest-ncVoters5M.yml @@ -15,7 +15,7 @@ jobs: INPUT: "zingg_community_performance_reports/perf_test/perfTestInput_ncVoters5M.json" steps: - name: checkout repo content - uses: actions/checkout@v3 # checkout the repository content to github runner. + uses: actions/checkout@v4 # checkout the repository content to github runner. - name: Load Spark config id: cfg run: | @@ -25,7 +25,7 @@ jobs: echo "hadoop_version=$HADOOP_VERSION" >> "$GITHUB_OUTPUT" echo "spark_url=$SPARK_URL" >> "$GITHUB_OUTPUT" - name: setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.12.7 #install the python needed - uses: actions/setup-java@v4 @@ -63,7 +63,7 @@ jobs: git remote set-url origin https://x-access-token:${{ secrets.GH_PERF_COMMUNITY_ACTION }}@github.com/zinggAI/zingg_community_performance_reports.git git config user.name sonalgoyal git config user.email sonalgoyal4@gmail.com - git add perf_test/perf_test_report/*.json + git add perf_test/perf_test_report/*.json perf_test/perf_test_report/*.csv if git diff --cached --quiet; then echo "No changes to commit for perfTest-febrl120K." else diff --git a/.github/workflows/pmd.yml b/.github/workflows/pmd.yml index 2cbea3f66..4979eb525 100644 --- a/.github/workflows/pmd.yml +++ b/.github/workflows/pmd.yml @@ -16,8 +16,8 @@ jobs: contents: read runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '11' From ade6f2a494e6d388df17dd3b938eb7d44a326a27 Mon Sep 17 00:00:00 2001 From: Chandan Date: Tue, 7 Jul 2026 09:05:21 +0530 Subject: [PATCH 004/144] Updates the Docker base image to apache/spark:3.5.5-python3 Co-authored-by: chandan Co-authored-by: Claude Fable 5 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e5da16865..bdd555f6e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ #FROM docker.io/bitnami/spark:3.1.2 -FROM apache/spark:3.5.0-python3 +FROM apache/spark:3.5.5-python3 USER 0 RUN apt-get update && \ apt install -y curl vim From 87608f24a6c96c9627b356df38fc1b9fae63ce50 Mon Sep 17 00:00:00 2001 From: Chandan Date: Wed, 8 Jul 2026 10:16:43 +0530 Subject: [PATCH 005/144] some minor changes (#1334) Co-authored-by: chandan --- .github/workflows/perfTest-febrl120K.yml | 8 +++++++- .github/workflows/perfTest-ncVoters5M.yml | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/perfTest-febrl120K.yml b/.github/workflows/perfTest-febrl120K.yml index 2b0744ff6..a5fa48f04 100644 --- a/.github/workflows/perfTest-febrl120K.yml +++ b/.github/workflows/perfTest-febrl120K.yml @@ -51,11 +51,17 @@ jobs: git clone https://x-access-token:${{ secrets.GH_PERF_COMMUNITY_ACTION }}@github.com/zinggAI/zingg_community_performance_reports.git echo "Listing inside cloned repo:" ls -al zingg_community_performance_reports || true + - name: Install plotting dependencies + run: | + pip install pandas matplotlib - name: download and execute py script run: | wget -O perfTestRunner.py https://raw.githubusercontent.com/zinggAI/zingg_performance/main/perfTestRunner.py + wget -O plot_result.py https://raw.githubusercontent.com/zinggAI/zingg_performance/main/plot_result.py echo "Running script..." python perfTestRunner.py + echo "Plotting results..." + python plot_result.py - name: Commit test results if: always() run: | @@ -63,7 +69,7 @@ jobs: git remote set-url origin https://x-access-token:${{ secrets.GH_PERF_COMMUNITY_ACTION }}@github.com/zinggAI/zingg_community_performance_reports.git git config user.name sonalgoyal git config user.email sonalgoyal4@gmail.com - git add perf_test/perf_test_report/*.json perf_test/perf_test_report/*.csv + git add perf_test/perf_test_report/ if git diff --cached --quiet; then echo "No changes to commit for perfTest-febrl120K." else diff --git a/.github/workflows/perfTest-ncVoters5M.yml b/.github/workflows/perfTest-ncVoters5M.yml index 454a7ddcf..5471ea3e7 100644 --- a/.github/workflows/perfTest-ncVoters5M.yml +++ b/.github/workflows/perfTest-ncVoters5M.yml @@ -51,11 +51,17 @@ jobs: git clone https://x-access-token:${{ secrets.GH_PERF_COMMUNITY_ACTION }}@github.com/zinggAI/zingg_community_performance_reports.git echo "Listing inside cloned repo:" ls -al zingg_community_performance_reports || true + - name: Install plotting dependencies + run: | + pip install pandas matplotlib - name: download and execute py script run: | wget -O perfTestRunner.py https://raw.githubusercontent.com/zinggAI/zingg_performance/main/perfTestRunner.py + wget -O plot_result.py https://raw.githubusercontent.com/zinggAI/zingg_performance/main/plot_result.py echo "Running script..." python perfTestRunner.py + echo "Plotting results..." + python plot_result.py - name: Commit test results if: always() run: | @@ -63,7 +69,7 @@ jobs: git remote set-url origin https://x-access-token:${{ secrets.GH_PERF_COMMUNITY_ACTION }}@github.com/zinggAI/zingg_community_performance_reports.git git config user.name sonalgoyal git config user.email sonalgoyal4@gmail.com - git add perf_test/perf_test_report/*.json perf_test/perf_test_report/*.csv + git add perf_test/perf_test_report/ if git diff --cached --quiet; then echo "No changes to commit for perfTest-febrl120K." else From 6bf436e9b1d97874a6f4119490508623aeba2b51 Mon Sep 17 00:00:00 2001 From: Chandan Date: Tue, 14 Jul 2026 17:05:56 +0530 Subject: [PATCH 006/144] Consolidate perf test workflows into a reusable workflow (#1346) Extract the shared steps of perfTest-febrl120K and perfTest-ncVoters5M into a single reusable workflow (perfTest-reusable.yml) invoked via workflow_call. The two dataset workflows become thin callers that pass their INPUT file and keep their own schedule. Also fix the copy-pasted 'No changes to commit for perfTest-febrl120K' message in the ncVoters workflow. Co-authored-by: chandan --- .github/workflows/perfTest-febrl120K.yml | 84 ++------------------ .github/workflows/perfTest-ncVoters5M.yml | 84 ++------------------ .github/workflows/perfTest-reusable.yml | 94 +++++++++++++++++++++++ 3 files changed, 106 insertions(+), 156 deletions(-) create mode 100644 .github/workflows/perfTest-reusable.yml diff --git a/.github/workflows/perfTest-febrl120K.yml b/.github/workflows/perfTest-febrl120K.yml index a5fa48f04..a4b2ca158 100644 --- a/.github/workflows/perfTest-febrl120K.yml +++ b/.github/workflows/perfTest-febrl120K.yml @@ -4,83 +4,11 @@ on: workflow_dispatch: schedule: - cron: "0 0 */3 * *" # runs every 3 days at 00 AM UTC + jobs: perf-test: - name: performance-test-febrl120K - runs-on: ubuntu-latest - env: - SPARK_MASTER: local[*] - ZINGG_HOME: assembly/target - ZINGG_USER: zingg_user - INPUT: "zingg_community_performance_reports/perf_test/perfTestInput_febrl120K.json" - steps: - - name: checkout repo content - uses: actions/checkout@v4 # checkout the repository content to github runner. - - name: Load Spark config - id: cfg - run: | - set -a #Automatically marks all subsequently defined variables for export - source spark.env - echo "spark_version=$SPARK_VERSION" >> "$GITHUB_OUTPUT" - echo "hadoop_version=$HADOOP_VERSION" >> "$GITHUB_OUTPUT" - echo "spark_url=$SPARK_URL" >> "$GITHUB_OUTPUT" - - name: setup python - uses: actions/setup-python@v5 - with: - python-version: 3.12.7 #install the python needed - - uses: actions/setup-java@v4 - with: - java-version: '11' - distribution: temurin - - name: setup spark - uses: vemonet/setup-spark@v1 - with: - spark-version: ${{ steps.cfg.outputs.spark_version }} - hadoop-version: ${{ steps.cfg.outputs.hadoop_version }} - spark-url: ${{ steps.cfg.outputs.spark_url }} - - name: check spark - run: spark-submit --version - - name: mvn clean - run: mvn clean - - name: Build with Maven - run: mvn clean compile package -DskipTests=true - - name: Clone performance reports repo - run: | - set -e #Exit immediately if a command exits with a non-zero status. - echo "Cloning performance reports repo..." - git clone https://x-access-token:${{ secrets.GH_PERF_COMMUNITY_ACTION }}@github.com/zinggAI/zingg_community_performance_reports.git - echo "Listing inside cloned repo:" - ls -al zingg_community_performance_reports || true - - name: Install plotting dependencies - run: | - pip install pandas matplotlib - - name: download and execute py script - run: | - wget -O perfTestRunner.py https://raw.githubusercontent.com/zinggAI/zingg_performance/main/perfTestRunner.py - wget -O plot_result.py https://raw.githubusercontent.com/zinggAI/zingg_performance/main/plot_result.py - echo "Running script..." - python perfTestRunner.py - echo "Plotting results..." - python plot_result.py - - name: Commit test results - if: always() - run: | - cd zingg_community_performance_reports - git remote set-url origin https://x-access-token:${{ secrets.GH_PERF_COMMUNITY_ACTION }}@github.com/zinggAI/zingg_community_performance_reports.git - git config user.name sonalgoyal - git config user.email sonalgoyal4@gmail.com - git add perf_test/perf_test_report/ - if git diff --cached --quiet; then - echo "No changes to commit for perfTest-febrl120K." - else - echo "Committing changes..." - git commit -m "report generated" - - echo "Fetching remote..." - git fetch origin main - - echo "Rebasing onto origin/main..." - git rebase origin/main || (echo "Rebase failed; resetting hard to origin/main" && git rebase --abort && git reset --hard origin/main) - echo "Attempting push..." - git push origin HEAD:main - fi + uses: ./.github/workflows/perfTest-reusable.yml + with: + test_name: performance-test-febrl120K + input_file: "zingg_community_performance_reports/perf_test/perfTestInput_febrl120K.json" + secrets: inherit diff --git a/.github/workflows/perfTest-ncVoters5M.yml b/.github/workflows/perfTest-ncVoters5M.yml index 5471ea3e7..b8a1ba54c 100644 --- a/.github/workflows/perfTest-ncVoters5M.yml +++ b/.github/workflows/perfTest-ncVoters5M.yml @@ -4,83 +4,11 @@ on: workflow_dispatch: schedule: - cron: "0 3 */3 * *" # runs every 3 days at 3 AM UTC + jobs: perf-test: - name: performance-test-ncVoters5M - runs-on: ubuntu-latest - env: - SPARK_MASTER: local[*] - ZINGG_HOME: assembly/target - ZINGG_USER: zingg_user - INPUT: "zingg_community_performance_reports/perf_test/perfTestInput_ncVoters5M.json" - steps: - - name: checkout repo content - uses: actions/checkout@v4 # checkout the repository content to github runner. - - name: Load Spark config - id: cfg - run: | - set -a #Automatically marks all subsequently defined variables for export - source spark.env - echo "spark_version=$SPARK_VERSION" >> "$GITHUB_OUTPUT" - echo "hadoop_version=$HADOOP_VERSION" >> "$GITHUB_OUTPUT" - echo "spark_url=$SPARK_URL" >> "$GITHUB_OUTPUT" - - name: setup python - uses: actions/setup-python@v5 - with: - python-version: 3.12.7 #install the python needed - - uses: actions/setup-java@v4 - with: - java-version: '11' - distribution: temurin - - name: setup spark - uses: vemonet/setup-spark@v1 - with: - spark-version: ${{ steps.cfg.outputs.spark_version }} - hadoop-version: ${{ steps.cfg.outputs.hadoop_version }} - spark-url: ${{ steps.cfg.outputs.spark_url }} - - name: check spark - run: spark-submit --version - - name: mvn clean - run: mvn clean - - name: Build with Maven - run: mvn clean compile package -DskipTests=true - - name: Clone performance reports repo - run: | - set -e #Exit immediately if a command exits with a non-zero status. - echo "Cloning performance reports repo..." - git clone https://x-access-token:${{ secrets.GH_PERF_COMMUNITY_ACTION }}@github.com/zinggAI/zingg_community_performance_reports.git - echo "Listing inside cloned repo:" - ls -al zingg_community_performance_reports || true - - name: Install plotting dependencies - run: | - pip install pandas matplotlib - - name: download and execute py script - run: | - wget -O perfTestRunner.py https://raw.githubusercontent.com/zinggAI/zingg_performance/main/perfTestRunner.py - wget -O plot_result.py https://raw.githubusercontent.com/zinggAI/zingg_performance/main/plot_result.py - echo "Running script..." - python perfTestRunner.py - echo "Plotting results..." - python plot_result.py - - name: Commit test results - if: always() - run: | - cd zingg_community_performance_reports - git remote set-url origin https://x-access-token:${{ secrets.GH_PERF_COMMUNITY_ACTION }}@github.com/zinggAI/zingg_community_performance_reports.git - git config user.name sonalgoyal - git config user.email sonalgoyal4@gmail.com - git add perf_test/perf_test_report/ - if git diff --cached --quiet; then - echo "No changes to commit for perfTest-febrl120K." - else - echo "Committing changes..." - git commit -m "report generated" - - echo "Fetching remote..." - git fetch origin main - - echo "Rebasing onto origin/main..." - git rebase origin/main || (echo "Rebase failed; resetting hard to origin/main" && git rebase --abort && git reset --hard origin/main) - echo "Attempting push..." - git push origin HEAD:main - fi + uses: ./.github/workflows/perfTest-reusable.yml + with: + test_name: performance-test-ncVoters5M + input_file: "zingg_community_performance_reports/perf_test/perfTestInput_ncVoters5M.json" + secrets: inherit diff --git a/.github/workflows/perfTest-reusable.yml b/.github/workflows/perfTest-reusable.yml new file mode 100644 index 000000000..ae2b57bc8 --- /dev/null +++ b/.github/workflows/perfTest-reusable.yml @@ -0,0 +1,94 @@ +name: performance-test-reusable + +on: + workflow_call: + inputs: + test_name: + description: "Name of the performance test (used for the job name and logs)" + required: true + type: string + input_file: + description: "Path to the perf test input JSON (passed to the runner as INPUT)" + required: true + type: string + +jobs: + perf-test: + name: ${{ inputs.test_name }} + runs-on: ubuntu-latest + env: + SPARK_MASTER: local[*] + ZINGG_HOME: assembly/target + ZINGG_USER: zingg_user + INPUT: ${{ inputs.input_file }} + steps: + - name: checkout repo content + uses: actions/checkout@v4 # checkout the repository content to github runner. + - name: Load Spark config + id: cfg + run: | + set -a #Automatically marks all subsequently defined variables for export + source spark.env + echo "spark_version=$SPARK_VERSION" >> "$GITHUB_OUTPUT" + echo "hadoop_version=$HADOOP_VERSION" >> "$GITHUB_OUTPUT" + echo "spark_url=$SPARK_URL" >> "$GITHUB_OUTPUT" + - name: setup python + uses: actions/setup-python@v5 + with: + python-version: 3.12.7 #install the python needed + - uses: actions/setup-java@v4 + with: + java-version: '11' + distribution: temurin + - name: setup spark + uses: vemonet/setup-spark@v1 + with: + spark-version: ${{ steps.cfg.outputs.spark_version }} + hadoop-version: ${{ steps.cfg.outputs.hadoop_version }} + spark-url: ${{ steps.cfg.outputs.spark_url }} + - name: check spark + run: spark-submit --version + - name: mvn clean + run: mvn clean + - name: Build with Maven + run: mvn clean compile package -DskipTests=true + - name: Clone performance reports repo + run: | + set -e #Exit immediately if a command exits with a non-zero status. + echo "Cloning performance reports repo..." + git clone https://x-access-token:${{ secrets.GH_PERF_COMMUNITY_ACTION }}@github.com/zinggAI/zingg_community_performance_reports.git + echo "Listing inside cloned repo:" + ls -al zingg_community_performance_reports || true + - name: Install plotting dependencies + run: | + pip install pandas matplotlib + - name: download and execute py script + run: | + wget -O perfTestRunner.py https://raw.githubusercontent.com/zinggAI/zingg_performance/main/perfTestRunner.py + wget -O plot_result.py https://raw.githubusercontent.com/zinggAI/zingg_performance/main/plot_result.py + echo "Running script..." + python perfTestRunner.py + echo "Plotting results..." + python plot_result.py + - name: Commit test results + if: always() + run: | + cd zingg_community_performance_reports + git remote set-url origin https://x-access-token:${{ secrets.GH_PERF_COMMUNITY_ACTION }}@github.com/zinggAI/zingg_community_performance_reports.git + git config user.name sonalgoyal + git config user.email sonalgoyal4@gmail.com + git add perf_test/perf_test_report/ + if git diff --cached --quiet; then + echo "No changes to commit for ${{ inputs.test_name }}." + else + echo "Committing changes..." + git commit -m "report generated" + + echo "Fetching remote..." + git fetch origin main + + echo "Rebasing onto origin/main..." + git rebase origin/main || (echo "Rebase failed; resetting hard to origin/main" && git rebase --abort && git reset --hard origin/main) + echo "Attempting push..." + git push origin HEAD:main + fi From 9a21894158fce2e86ba34ae8dc28ef1731378057 Mon Sep 17 00:00:00 2001 From: Chandan Date: Tue, 14 Jul 2026 17:09:24 +0530 Subject: [PATCH 007/144] Remove stale unused name field from Matcher and Linker (#682) (#1344) The abstract Matcher and Linker classes carried a leftover `static String name` field ("zingg.Matcher" / "zingg.Linker") from the original single-package layout. After the multi-module refactor these classes moved to zingg.common.core.executor, so the value no longer reflects the package structure. The field is also unused: the phase factory (e.g. SparkZFactory) loads executors by reflection using the concrete engine classes' own name field (SparkMatcher.name, SparkLinker.name, ...), which are correct. The abstract base field is never read and, being abstract, could never be instantiated via that path. Removing it resolves the "names are incorrect" issue and drops dead, misleading code. Co-authored-by: chandan --- .../core/src/main/java/zingg/common/core/executor/Linker.java | 1 - .../core/src/main/java/zingg/common/core/executor/Matcher.java | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/common/core/src/main/java/zingg/common/core/executor/Linker.java b/common/core/src/main/java/zingg/common/core/executor/Linker.java index 986880a22..cf05bdea0 100644 --- a/common/core/src/main/java/zingg/common/core/executor/Linker.java +++ b/common/core/src/main/java/zingg/common/core/executor/Linker.java @@ -16,7 +16,6 @@ public abstract class Linker extends Matcher { private static final long serialVersionUID = 1L; - protected static String name = "zingg.Linker"; public static final Log LOG = LogFactory.getLog(Linker.class); public Linker() { diff --git a/common/core/src/main/java/zingg/common/core/executor/Matcher.java b/common/core/src/main/java/zingg/common/core/executor/Matcher.java index 0eddcc337..3be666ebb 100644 --- a/common/core/src/main/java/zingg/common/core/executor/Matcher.java +++ b/common/core/src/main/java/zingg/common/core/executor/Matcher.java @@ -31,8 +31,7 @@ public abstract class Matcher extends ZinggBase implements IPreprocessors { private static final long serialVersionUID = 1L; - protected static String name = "zingg.Matcher"; - public static final Log LOG = LogFactory.getLog(Matcher.class); + public static final Log LOG = LogFactory.getLog(Matcher.class); protected IMatchOutputBuilder matchOutputBuilder; ZFrame output = null; boolean toWrite = true; From 453e0885f50785461c1d005e1ae1abb70a945244 Mon Sep 17 00:00:00 2001 From: Chandan Date: Tue, 14 Jul 2026 17:36:37 +0530 Subject: [PATCH 008/144] Upgrade GraphFrames from 0.10.0 to 0.11.0 (#1335) Co-authored-by: chandan --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 70984b79a..06da48cb4 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ 2.12.17 3.4 2.12 - 0.10.0 + 0.11.0 @@ -59,7 +59,7 @@ 2.12.10 3.5 2.12 - 0.10.0 + 0.11.0 From a965f68cca058a341c026b550b12169f6ebd5020 Mon Sep 17 00:00:00 2001 From: Chandan Date: Tue, 28 Jul 2026 15:51:09 +0530 Subject: [PATCH 009/144] Test spark lite heavy (#1348) * Split Spark test session provider into Lite and Heavy profiles * use a flag instead of getInstance as suggested in review dropped getInstance from the providers and used the isSessionInitialised flag in the extension to build the session once. made initializeSession public. memory stays the same, lite 1g and heavy 8g. --------- Co-authored-by: chandan --- spark/core/pom.xml | 9 +++- .../java/zingg/spark/core/TestImageType.java | 2 +- .../zingg/spark/core/TestSparkBaseHeavy.java | 54 +++++++++++++++++++ ...tSparkBase.java => TestSparkBaseLite.java} | 13 ++--- .../spark/core/block/TestSparkBlock.java | 4 +- .../core/block/TestSparkBlockingTreeUtil.java | 4 +- .../documenter/TestSparkDataDocumenter.java | 4 +- .../core/documenter/TestSparkDocumenter.java | 4 +- .../documenter/TestSparkModelDocumenter.java | 4 +- .../executor/TestSparkExecutorsCompound.java | 4 +- .../executor/TestSparkExecutorsSingle.java | 4 +- .../executor/trainer/TestSparkTrainer.java | 4 +- .../java/zingg/spark/core/hash/TestGetAs.java | 4 +- .../spark/core/model/TestSparkModel.java | 4 +- .../preprocess/TestSparkPreprocessors.java | 4 +- .../TestSparkCaseNormalizer.java | 4 +- .../stopwords/TestSparkStopWordsRemover.java | 4 +- .../trim/TestSparkTrimPreprocess.java | 4 +- .../TestSparkStopWordsRecommender.java | 4 +- ...der.java => SparkSessionProviderBase.java} | 22 +++----- .../session/SparkSessionProviderHeavy.java | 9 ++++ .../session/SparkSessionProviderLite.java | 9 ++++ .../spark/core/sparkFrame/TestSparkFrame.java | 4 +- .../zingg/spark/core/util/TestPipeUtil.java | 4 +- .../spark/core/util/TestSparkDSUtil.java | 4 +- .../util/TestSparkVerticalDisplayUtility.java | 4 +- ...ingg.properties => zingg-heavy.properties} | 0 .../src/test/resources/zingg-lite.properties | 4 ++ 28 files changed, 137 insertions(+), 61 deletions(-) create mode 100644 spark/core/src/test/java/zingg/spark/core/TestSparkBaseHeavy.java rename spark/core/src/test/java/zingg/spark/core/{TestSparkBase.java => TestSparkBaseLite.java} (79%) rename spark/core/src/test/java/zingg/spark/core/session/{SparkSessionProvider.java => SparkSessionProviderBase.java} (84%) create mode 100644 spark/core/src/test/java/zingg/spark/core/session/SparkSessionProviderHeavy.java create mode 100644 spark/core/src/test/java/zingg/spark/core/session/SparkSessionProviderLite.java rename spark/core/src/test/resources/{zingg.properties => zingg-heavy.properties} (100%) create mode 100644 spark/core/src/test/resources/zingg-lite.properties diff --git a/spark/core/pom.xml b/spark/core/pom.xml index 82aa0f55d..e3390264a 100644 --- a/spark/core/pom.xml +++ b/spark/core/pom.xml @@ -66,7 +66,14 @@ - + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + diff --git a/spark/core/src/test/java/zingg/spark/core/TestImageType.java b/spark/core/src/test/java/zingg/spark/core/TestImageType.java index 634a1a023..d87a4ab1c 100644 --- a/spark/core/src/test/java/zingg/spark/core/TestImageType.java +++ b/spark/core/src/test/java/zingg/spark/core/TestImageType.java @@ -21,7 +21,7 @@ import zingg.common.core.similarity.function.ArrayDoubleSimilarityFunction; import zingg.spark.core.util.SparkFnRegistrar; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestImageType { diff --git a/spark/core/src/test/java/zingg/spark/core/TestSparkBaseHeavy.java b/spark/core/src/test/java/zingg/spark/core/TestSparkBaseHeavy.java new file mode 100644 index 000000000..226aada25 --- /dev/null +++ b/spark/core/src/test/java/zingg/spark/core/TestSparkBaseHeavy.java @@ -0,0 +1,54 @@ +package zingg.spark.core; + +import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import zingg.common.client.arguments.model.IArguments; +import zingg.spark.core.session.SparkSessionProviderHeavy; +import zingg.spark.core.context.ZinggSparkContext; + +public class TestSparkBaseHeavy implements BeforeAllCallback, AfterAllCallback, ParameterResolver { + public static IArguments args; + public static JavaSparkContext ctx; + public static SparkSession spark; + public static ZinggSparkContext zsCTX; + static boolean isSessionInitialised = false; + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return parameterContext.getParameter().getType() + .equals(SparkSession.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return spark; + } + + @Override + public void afterAll(ExtensionContext context) { + + } + + @Override + public void beforeAll(ExtensionContext context) { + if (!isSessionInitialised) { + SparkSessionProviderHeavy sparkSessionProvider = new SparkSessionProviderHeavy(); + sparkSessionProvider.initializeSession(); + spark = sparkSessionProvider.getSparkSession(); + ctx = sparkSessionProvider.getJavaSparkContext(); + args = sparkSessionProvider.getArgs(); + zsCTX = sparkSessionProvider.getZinggSparkContext(); + isSessionInitialised = true; + } + } + + +} diff --git a/spark/core/src/test/java/zingg/spark/core/TestSparkBase.java b/spark/core/src/test/java/zingg/spark/core/TestSparkBaseLite.java similarity index 79% rename from spark/core/src/test/java/zingg/spark/core/TestSparkBase.java rename to spark/core/src/test/java/zingg/spark/core/TestSparkBaseLite.java index 66498deab..5119597b0 100644 --- a/spark/core/src/test/java/zingg/spark/core/TestSparkBase.java +++ b/spark/core/src/test/java/zingg/spark/core/TestSparkBaseLite.java @@ -9,15 +9,15 @@ import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import zingg.common.client.arguments.model.IArguments; -import zingg.spark.core.session.SparkSessionProvider; +import zingg.spark.core.session.SparkSessionProviderLite; import zingg.spark.core.context.ZinggSparkContext; -public class TestSparkBase implements BeforeAllCallback, AfterAllCallback, ParameterResolver { +public class TestSparkBaseLite implements BeforeAllCallback, AfterAllCallback, ParameterResolver { public static IArguments args; public static JavaSparkContext ctx; public static SparkSession spark; public static ZinggSparkContext zsCTX; - static boolean isSetUp = false; + static boolean isSessionInitialised = false; @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) @@ -39,14 +39,15 @@ public void afterAll(ExtensionContext context) { @Override public void beforeAll(ExtensionContext context) { - if (!isSetUp || spark == null) { - SparkSessionProvider sparkSessionProvider = SparkSessionProvider.getInstance(); + if (!isSessionInitialised) { + SparkSessionProviderLite sparkSessionProvider = new SparkSessionProviderLite(); + sparkSessionProvider.initializeSession(); spark = sparkSessionProvider.getSparkSession(); ctx = sparkSessionProvider.getJavaSparkContext(); args = sparkSessionProvider.getArgs(); zsCTX = sparkSessionProvider.getZinggSparkContext(); + isSessionInitialised = true; } - isSetUp = true; } diff --git a/spark/core/src/test/java/zingg/spark/core/block/TestSparkBlock.java b/spark/core/src/test/java/zingg/spark/core/block/TestSparkBlock.java index 62a502a67..2b4c50737 100644 --- a/spark/core/src/test/java/zingg/spark/core/block/TestSparkBlock.java +++ b/spark/core/src/test/java/zingg/spark/core/block/TestSparkBlock.java @@ -7,7 +7,7 @@ import org.apache.spark.sql.types.DataType; import org.junit.jupiter.api.extension.ExtendWith; import zingg.common.core.block.TestBlockBase; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.common.client.ZinggClientException; import zingg.common.client.util.IWithSession; import zingg.common.client.util.WithSession; @@ -16,7 +16,7 @@ import zingg.spark.core.util.SparkBlockingTreeUtil; import zingg.spark.core.util.SparkHashUtil; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkBlock extends TestBlockBase, Row, Column, DataType> { public static ZinggSparkContext zsCTX = new ZinggSparkContext(); diff --git a/spark/core/src/test/java/zingg/spark/core/block/TestSparkBlockingTreeUtil.java b/spark/core/src/test/java/zingg/spark/core/block/TestSparkBlockingTreeUtil.java index 4a736ed7a..5d8b7776d 100644 --- a/spark/core/src/test/java/zingg/spark/core/block/TestSparkBlockingTreeUtil.java +++ b/spark/core/src/test/java/zingg/spark/core/block/TestSparkBlockingTreeUtil.java @@ -19,11 +19,11 @@ import zingg.common.core.util.HashUtil; import zingg.spark.client.util.SparkDFObjectUtil; import zingg.spark.client.util.SparkPipeUtil; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.spark.core.util.SparkBlockingTreeUtil; import zingg.spark.core.util.SparkHashUtil; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkBlockingTreeUtil extends TestBlockingTreeUtil, Row, Column, DataType>{ private final IWithSession withSession; diff --git a/spark/core/src/test/java/zingg/spark/core/documenter/TestSparkDataDocumenter.java b/spark/core/src/test/java/zingg/spark/core/documenter/TestSparkDataDocumenter.java index 4dcdead38..b3eaaa4c0 100644 --- a/spark/core/src/test/java/zingg/spark/core/documenter/TestSparkDataDocumenter.java +++ b/spark/core/src/test/java/zingg/spark/core/documenter/TestSparkDataDocumenter.java @@ -13,13 +13,13 @@ import zingg.common.core.context.IContext; import zingg.common.core.documenter.DataDocumenter; import zingg.common.core.documenter.TestDataDocumenterBase; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.common.client.ClientOptions; import zingg.common.client.arguments.model.IArguments; import zingg.common.client.ZinggClientException; import zingg.spark.core.context.ZinggSparkContext; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkDataDocumenter extends TestDataDocumenterBase, Row, Column, DataType> { public static final Log LOG = LogFactory.getLog(TestSparkDataDocumenter.class); diff --git a/spark/core/src/test/java/zingg/spark/core/documenter/TestSparkDocumenter.java b/spark/core/src/test/java/zingg/spark/core/documenter/TestSparkDocumenter.java index 200038ece..9039401e2 100644 --- a/spark/core/src/test/java/zingg/spark/core/documenter/TestSparkDocumenter.java +++ b/spark/core/src/test/java/zingg/spark/core/documenter/TestSparkDocumenter.java @@ -14,10 +14,10 @@ import zingg.common.core.context.IContext; import zingg.common.core.documenter.DocumenterBase; import zingg.common.core.documenter.TestDocumenterBase; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.spark.core.context.ZinggSparkContext; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkDocumenter extends TestDocumenterBase, Row, Column, DataType>{ public static final Log LOG = LogFactory.getLog(TestSparkDocumenter.class); diff --git a/spark/core/src/test/java/zingg/spark/core/documenter/TestSparkModelDocumenter.java b/spark/core/src/test/java/zingg/spark/core/documenter/TestSparkModelDocumenter.java index a75561426..cef9fc2dd 100644 --- a/spark/core/src/test/java/zingg/spark/core/documenter/TestSparkModelDocumenter.java +++ b/spark/core/src/test/java/zingg/spark/core/documenter/TestSparkModelDocumenter.java @@ -18,11 +18,11 @@ import zingg.common.core.documenter.ModelDocumenter; import zingg.common.core.documenter.TestModelDocumenterBase; import zingg.spark.client.SparkFrame; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.spark.core.context.ZinggSparkContext; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkModelDocumenter extends TestModelDocumenterBase, Row, Column, DataType> { public static final Log LOG = LogFactory.getLog(TestSparkModelDocumenter.class); diff --git a/spark/core/src/test/java/zingg/spark/core/executor/TestSparkExecutorsCompound.java b/spark/core/src/test/java/zingg/spark/core/executor/TestSparkExecutorsCompound.java index 5ce9a0711..aef6f5942 100644 --- a/spark/core/src/test/java/zingg/spark/core/executor/TestSparkExecutorsCompound.java +++ b/spark/core/src/test/java/zingg/spark/core/executor/TestSparkExecutorsCompound.java @@ -19,13 +19,13 @@ import zingg.common.core.executor.TrainMatcher; import zingg.common.core.util.ICleanUpUtil; import zingg.spark.client.util.SparkDFObjectUtil; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseHeavy; import zingg.spark.core.context.ZinggSparkContext; import zingg.spark.core.executor.labeller.ProgrammaticSparkLabeller; import zingg.spark.core.executor.validate.SparkTrainMatchValidator; import zingg.spark.core.util.SparkCleanUpUtil; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseHeavy.class) public class TestSparkExecutorsCompound extends TestExecutorsCompound,Row,Column,DataType> { protected static final String CONFIG_FILE = "zingg/spark/core/executor/compound/configSparkIntTest.json"; protected static final String TEST_DATA_FILE = "zingg/spark/core/executor/test.csv"; diff --git a/spark/core/src/test/java/zingg/spark/core/executor/TestSparkExecutorsSingle.java b/spark/core/src/test/java/zingg/spark/core/executor/TestSparkExecutorsSingle.java index 00058de90..bc245e31c 100644 --- a/spark/core/src/test/java/zingg/spark/core/executor/TestSparkExecutorsSingle.java +++ b/spark/core/src/test/java/zingg/spark/core/executor/TestSparkExecutorsSingle.java @@ -20,13 +20,13 @@ import zingg.common.core.executor.Trainer; import zingg.common.core.util.ICleanUpUtil; import zingg.spark.client.util.SparkDFObjectUtil; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseHeavy; import zingg.spark.core.context.ZinggSparkContext; import zingg.spark.core.executor.labeller.ProgrammaticSparkLabeller; import zingg.spark.core.executor.validate.SparkTrainerValidator; import zingg.spark.core.util.SparkCleanUpUtil; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseHeavy.class) public class TestSparkExecutorsSingle extends TestExecutorsSingle,Row,Column,DataType> { protected static final String CONFIG_FILE = "zingg/spark/core/executor/single/configSparkIntTest.json"; protected static final String CONFIGLINK_FILE = "zingg/spark/core/executor/single/configSparkLinkTest.json"; diff --git a/spark/core/src/test/java/zingg/spark/core/executor/trainer/TestSparkTrainer.java b/spark/core/src/test/java/zingg/spark/core/executor/trainer/TestSparkTrainer.java index 773ca122d..f8896c2e1 100644 --- a/spark/core/src/test/java/zingg/spark/core/executor/trainer/TestSparkTrainer.java +++ b/spark/core/src/test/java/zingg/spark/core/executor/trainer/TestSparkTrainer.java @@ -12,12 +12,12 @@ import zingg.common.core.executor.Trainer; import zingg.common.core.executor.trainer.TestTrainer; import zingg.common.core.executor.trainer.util.IDataFrameUtility; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.spark.core.context.ZinggSparkContext; import zingg.spark.core.executor.SparkTrainer; import zingg.spark.core.executor.trainer.util.SparkDataFrameUtility; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkTrainer extends TestTrainer, Row, Column, DataType> { private ZinggSparkContext zinggSparkContext; diff --git a/spark/core/src/test/java/zingg/spark/core/hash/TestGetAs.java b/spark/core/src/test/java/zingg/spark/core/hash/TestGetAs.java index c9c21f4c1..6bc9b2779 100644 --- a/spark/core/src/test/java/zingg/spark/core/hash/TestGetAs.java +++ b/spark/core/src/test/java/zingg/spark/core/hash/TestGetAs.java @@ -14,11 +14,11 @@ import org.apache.spark.sql.types.StructField; import org.apache.spark.sql.types.StructType; import org.junit.jupiter.api.Test; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import org.junit.jupiter.api.extension.ExtendWith; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestGetAs { protected static final String FIELD_DOUBLE = "fieldDouble"; diff --git a/spark/core/src/test/java/zingg/spark/core/model/TestSparkModel.java b/spark/core/src/test/java/zingg/spark/core/model/TestSparkModel.java index 22884269f..8386b7270 100644 --- a/spark/core/src/test/java/zingg/spark/core/model/TestSparkModel.java +++ b/spark/core/src/test/java/zingg/spark/core/model/TestSparkModel.java @@ -13,12 +13,12 @@ import zingg.common.core.model.TestModelBase; import zingg.common.core.util.ModelUtil; import zingg.spark.client.util.SparkDFObjectUtil; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.spark.core.context.ZinggSparkContext; import zingg.spark.core.util.SparkModelUtil; import org.junit.jupiter.api.extension.ExtendWith; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkModel extends TestModelBase, Row, Column, DataType> { private SparkSession sparkSession; diff --git a/spark/core/src/test/java/zingg/spark/core/preprocess/TestSparkPreprocessors.java b/spark/core/src/test/java/zingg/spark/core/preprocess/TestSparkPreprocessors.java index 348b580b6..6669ec7dd 100644 --- a/spark/core/src/test/java/zingg/spark/core/preprocess/TestSparkPreprocessors.java +++ b/spark/core/src/test/java/zingg/spark/core/preprocess/TestSparkPreprocessors.java @@ -7,7 +7,7 @@ import org.apache.spark.sql.types.DataType; import org.junit.jupiter.api.extension.ExtendWith; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.common.client.arguments.model.IZArgs; import zingg.common.client.ZinggClientException; import zingg.common.client.util.IWithSession; @@ -19,7 +19,7 @@ import zingg.spark.client.util.SparkDFObjectUtil; import zingg.spark.core.context.ZinggSparkContext; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkPreprocessors extends TestPreprocessors, Row, Column, DataType> { public static IWithSession iWithSession = new WithSession(); diff --git a/spark/core/src/test/java/zingg/spark/core/preprocess/casenormalize/TestSparkCaseNormalizer.java b/spark/core/src/test/java/zingg/spark/core/preprocess/casenormalize/TestSparkCaseNormalizer.java index bf8430c4a..4f362fd89 100644 --- a/spark/core/src/test/java/zingg/spark/core/preprocess/casenormalize/TestSparkCaseNormalizer.java +++ b/spark/core/src/test/java/zingg/spark/core/preprocess/casenormalize/TestSparkCaseNormalizer.java @@ -14,13 +14,13 @@ import zingg.common.core.preprocess.casenormalize.CaseNormalizer; import zingg.common.core.preprocess.casenormalize.TestCaseNormalizer; import zingg.spark.client.util.SparkDFObjectUtil; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.spark.core.context.ZinggSparkContext; import zingg.spark.core.util.SparkCaseNormalizerUtility; import java.util.List; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkCaseNormalizer extends TestCaseNormalizer, Row, Column, DataType> { public static IWithSession iWithSession = new WithSession(); diff --git a/spark/core/src/test/java/zingg/spark/core/preprocess/stopwords/TestSparkStopWordsRemover.java b/spark/core/src/test/java/zingg/spark/core/preprocess/stopwords/TestSparkStopWordsRemover.java index 282195e66..cc69a8eda 100644 --- a/spark/core/src/test/java/zingg/spark/core/preprocess/stopwords/TestSparkStopWordsRemover.java +++ b/spark/core/src/test/java/zingg/spark/core/preprocess/stopwords/TestSparkStopWordsRemover.java @@ -6,7 +6,7 @@ import org.apache.spark.sql.SparkSession; import org.apache.spark.sql.types.DataType; import org.junit.jupiter.api.extension.ExtendWith; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.common.client.ZinggClientException; import zingg.common.client.util.IWithSession; import zingg.common.client.util.WithSession; @@ -15,7 +15,7 @@ import zingg.spark.client.util.SparkDFObjectUtil; import zingg.spark.core.context.ZinggSparkContext; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkStopWordsRemover extends TestStopWordsBase, Row, Column, DataType> { public static IWithSession iWithSession = new WithSession(); diff --git a/spark/core/src/test/java/zingg/spark/core/preprocess/trim/TestSparkTrimPreprocess.java b/spark/core/src/test/java/zingg/spark/core/preprocess/trim/TestSparkTrimPreprocess.java index 7266ae6a5..53743d7f3 100644 --- a/spark/core/src/test/java/zingg/spark/core/preprocess/trim/TestSparkTrimPreprocess.java +++ b/spark/core/src/test/java/zingg/spark/core/preprocess/trim/TestSparkTrimPreprocess.java @@ -16,11 +16,11 @@ import zingg.common.core.preprocess.trim.TestTrimPreprocess; import zingg.common.core.preprocess.trim.TrimPreprocessor; import zingg.spark.client.util.SparkDFObjectUtil; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.spark.core.context.ZinggSparkContext; import zingg.spark.core.util.SparkTrimPreprocessUtility; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkTrimPreprocess extends TestTrimPreprocess, Row, Column, DataType> { public static IWithSession iWithSession = new WithSession(); diff --git a/spark/core/src/test/java/zingg/spark/core/recommender/TestSparkStopWordsRecommender.java b/spark/core/src/test/java/zingg/spark/core/recommender/TestSparkStopWordsRecommender.java index a58b4a18d..470f1ba7e 100644 --- a/spark/core/src/test/java/zingg/spark/core/recommender/TestSparkStopWordsRecommender.java +++ b/spark/core/src/test/java/zingg/spark/core/recommender/TestSparkStopWordsRecommender.java @@ -20,10 +20,10 @@ import zingg.common.core.recommender.TestStopWordsRecommenderBase; import zingg.spark.client.SparkFrame; import zingg.spark.client.util.SparkDFObjectUtil; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.spark.core.context.ZinggSparkContext; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkStopWordsRecommender extends TestStopWordsRecommenderBase, Row, Column, DataType> { public static final Log LOG = LogFactory.getLog(TestSparkStopWordsRecommender.class); diff --git a/spark/core/src/test/java/zingg/spark/core/session/SparkSessionProvider.java b/spark/core/src/test/java/zingg/spark/core/session/SparkSessionProviderBase.java similarity index 84% rename from spark/core/src/test/java/zingg/spark/core/session/SparkSessionProvider.java rename to spark/core/src/test/java/zingg/spark/core/session/SparkSessionProviderBase.java index b487bf44e..f0841b1a8 100644 --- a/spark/core/src/test/java/zingg/spark/core/session/SparkSessionProvider.java +++ b/spark/core/src/test/java/zingg/spark/core/session/SparkSessionProviderBase.java @@ -13,17 +13,17 @@ import java.util.Properties; -public class SparkSessionProvider { - - private static SparkSessionProvider sparkSessionProvider; +public abstract class SparkSessionProviderBase { private SparkSession sparkSession; private JavaSparkContext javaSparkContext; private ZinggSparkContext zinggSparkContext; private IArguments args; - public static final Log LOG = LogFactory.getLog(SparkSessionProvider.class); + public static final Log LOG = LogFactory.getLog(SparkSessionProviderBase.class); + + protected abstract String getPropertiesFile(); - private void initializeSession() { + public void initializeSession() { if (sparkSession == null) { try { SparkSession.Builder builder = SparkSession @@ -31,7 +31,7 @@ private void initializeSession() { .master("local[*]") .appName("ZinggJunit"); Properties props = new Properties(); - props.load(getClass().getResourceAsStream("/zingg.properties")); + props.load(getClass().getResourceAsStream(getPropertiesFile())); for (String key : props.stringPropertyNames()) { builder = builder.config(key, props.getProperty(key)); } @@ -59,14 +59,6 @@ private void initializeSession() { } } - public static SparkSessionProvider getInstance() { - if (sparkSessionProvider == null) { - sparkSessionProvider = new SparkSessionProvider(); - sparkSessionProvider.initializeSession(); - } - return sparkSessionProvider; - } - //set getters public SparkSession getSparkSession() { return this.sparkSession; @@ -83,4 +75,4 @@ public ZinggSparkContext getZinggSparkContext() { public IArguments getArgs() { return this.args; } -} \ No newline at end of file +} diff --git a/spark/core/src/test/java/zingg/spark/core/session/SparkSessionProviderHeavy.java b/spark/core/src/test/java/zingg/spark/core/session/SparkSessionProviderHeavy.java new file mode 100644 index 000000000..241c9cdbf --- /dev/null +++ b/spark/core/src/test/java/zingg/spark/core/session/SparkSessionProviderHeavy.java @@ -0,0 +1,9 @@ +package zingg.spark.core.session; + +public class SparkSessionProviderHeavy extends SparkSessionProviderBase { + + @Override + protected String getPropertiesFile() { + return "/zingg-heavy.properties"; + } +} diff --git a/spark/core/src/test/java/zingg/spark/core/session/SparkSessionProviderLite.java b/spark/core/src/test/java/zingg/spark/core/session/SparkSessionProviderLite.java new file mode 100644 index 000000000..dba6e90cf --- /dev/null +++ b/spark/core/src/test/java/zingg/spark/core/session/SparkSessionProviderLite.java @@ -0,0 +1,9 @@ +package zingg.spark.core.session; + +public class SparkSessionProviderLite extends SparkSessionProviderBase { + + @Override + protected String getPropertiesFile() { + return "/zingg-lite.properties"; + } +} diff --git a/spark/core/src/test/java/zingg/spark/core/sparkFrame/TestSparkFrame.java b/spark/core/src/test/java/zingg/spark/core/sparkFrame/TestSparkFrame.java index 9caa1863d..973adff66 100644 --- a/spark/core/src/test/java/zingg/spark/core/sparkFrame/TestSparkFrame.java +++ b/spark/core/src/test/java/zingg/spark/core/sparkFrame/TestSparkFrame.java @@ -5,13 +5,13 @@ import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.junit.jupiter.api.extension.ExtendWith; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.common.client.util.IWithSession; import zingg.common.client.util.WithSession; import zingg.common.core.zFrame.TestZFrameBase; import zingg.spark.client.util.SparkDFObjectUtil; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkFrame extends TestZFrameBase, Row, Column> { public static IWithSession iWithSession = new WithSession(); diff --git a/spark/core/src/test/java/zingg/spark/core/util/TestPipeUtil.java b/spark/core/src/test/java/zingg/spark/core/util/TestPipeUtil.java index 1910d14aa..c81924e95 100644 --- a/spark/core/src/test/java/zingg/spark/core/util/TestPipeUtil.java +++ b/spark/core/src/test/java/zingg/spark/core/util/TestPipeUtil.java @@ -14,10 +14,10 @@ import zingg.common.client.ZinggClientException; import zingg.common.client.pipe.FilePipe; import zingg.common.client.pipe.Pipe; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.spark.core.context.ZinggSparkContext; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestPipeUtil { public static final Log LOG = LogFactory.getLog(TestPipeUtil.class); diff --git a/spark/core/src/test/java/zingg/spark/core/util/TestSparkDSUtil.java b/spark/core/src/test/java/zingg/spark/core/util/TestSparkDSUtil.java index bacd56eb4..9ab549e2a 100644 --- a/spark/core/src/test/java/zingg/spark/core/util/TestSparkDSUtil.java +++ b/spark/core/src/test/java/zingg/spark/core/util/TestSparkDSUtil.java @@ -13,14 +13,14 @@ import org.junit.jupiter.api.extension.ExtendWith; import zingg.spark.client.util.SparkDFObjectUtil; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.common.client.ZinggClientException; import zingg.common.client.util.IWithSession; import zingg.common.client.util.WithSession; import zingg.common.core.util.TestDSUtil; import zingg.spark.core.context.ZinggSparkContext; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkDSUtil extends TestDSUtil, Row, Column, DataType> { public static final Log LOG = LogFactory.getLog(TestSparkDSUtil.class); diff --git a/spark/core/src/test/java/zingg/spark/core/util/TestSparkVerticalDisplayUtility.java b/spark/core/src/test/java/zingg/spark/core/util/TestSparkVerticalDisplayUtility.java index dc6611b67..e5cf42d66 100644 --- a/spark/core/src/test/java/zingg/spark/core/util/TestSparkVerticalDisplayUtility.java +++ b/spark/core/src/test/java/zingg/spark/core/util/TestSparkVerticalDisplayUtility.java @@ -5,14 +5,14 @@ import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.junit.jupiter.api.extension.ExtendWith; -import zingg.spark.core.TestSparkBase; +import zingg.spark.core.TestSparkBaseLite; import zingg.common.client.util.IWithSession; import zingg.common.client.util.WithSession; import zingg.common.core.util.TestVerticalDisplayUtility; import zingg.spark.client.util.SparkDFObjectUtil; -@ExtendWith(TestSparkBase.class) +@ExtendWith(TestSparkBaseLite.class) public class TestSparkVerticalDisplayUtility extends TestVerticalDisplayUtility, Row, Column> { private IWithSession iWithSession = new WithSession(); diff --git a/spark/core/src/test/resources/zingg.properties b/spark/core/src/test/resources/zingg-heavy.properties similarity index 100% rename from spark/core/src/test/resources/zingg.properties rename to spark/core/src/test/resources/zingg-heavy.properties diff --git a/spark/core/src/test/resources/zingg-lite.properties b/spark/core/src/test/resources/zingg-lite.properties new file mode 100644 index 000000000..a4087ccb4 --- /dev/null +++ b/spark/core/src/test/resources/zingg-lite.properties @@ -0,0 +1,4 @@ +spark.executor.memory=1g +spark.driver.memory=1g +spark.sql.adaptive.enabled=false +spark.debug.maxToStringFields=100 From f76b2fa13e673257d7476e482a6f4a63a085b836 Mon Sep 17 00:00:00 2001 From: siddhik719 Date: Tue, 28 Jul 2026 15:56:03 +0530 Subject: [PATCH 010/144] Fix: dataFormat/outputFormat/numTotalFields/numMatchFields/stopWords missing from telemetry (#1342) * Fix: call track(boolean) in postMetrics() to populate missing telemetry fields The abstract track(boolean) method in ZinggBase sets dataFormat, outputFormat, numTotalFields, numMatchFields, and stopWords - but was never being called, since postMetrics() in the parent class ZinggBaseCommon had no reference to it. This left those 5 fields permanently missing from every telemetry event sent to GA4/BigQuery, confirmed via IDE (0 usages) and via live payload inspection. Fix: declared track(boolean) as abstract in ZinggBaseCommon (matching the existing pattern used for getArgs/setArgs/execute), and added a call to it in postMetrics() right before postEvent() sends the event. Verified locally: rebuilt from source, ran a match job with debug logging, confirmed all 5 previously-missing fields now appear correctly in the outgoing GA4 payload. * Revert unintended workflow file version drift (unrelated to telemetry fix) * Revert remaining workflow file version drift (unrelated to telemetry fix) --- .github/workflows/perfTest-febrl120K.yml | 18 +++++++++--------- .github/workflows/perfTest-ncVoters5M.yml | 18 +++++++++--------- .../common/core/executor/ZinggBaseCommon.java | 4 ++++ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/.github/workflows/perfTest-febrl120K.yml b/.github/workflows/perfTest-febrl120K.yml index a4b2ca158..7790ac7c7 100644 --- a/.github/workflows/perfTest-febrl120K.yml +++ b/.github/workflows/perfTest-febrl120K.yml @@ -1,14 +1,14 @@ name: performance-test-febrl120K on: - workflow_dispatch: - schedule: - - cron: "0 0 */3 * *" # runs every 3 days at 00 AM UTC +  workflow_dispatch: +  schedule: +    - cron: "0 0 */3 * *" # runs every 3 days at 00 AM UTC jobs: - perf-test: - uses: ./.github/workflows/perfTest-reusable.yml - with: - test_name: performance-test-febrl120K - input_file: "zingg_community_performance_reports/perf_test/perfTestInput_febrl120K.json" - secrets: inherit +  perf-test: +    uses: ./.github/workflows/perfTest-reusable.yml +    with: +      test_name: performance-test-febrl120K +      input_file: "zingg_community_performance_reports/perf_test/perfTestInput_febrl120K.json" +    secrets: inherit \ No newline at end of file diff --git a/.github/workflows/perfTest-ncVoters5M.yml b/.github/workflows/perfTest-ncVoters5M.yml index b8a1ba54c..5c8da2dd6 100644 --- a/.github/workflows/perfTest-ncVoters5M.yml +++ b/.github/workflows/perfTest-ncVoters5M.yml @@ -1,14 +1,14 @@ name: performance-test-ncVoters5M on: - workflow_dispatch: - schedule: - - cron: "0 3 */3 * *" # runs every 3 days at 3 AM UTC +  workflow_dispatch: +  schedule: +    - cron: "0 3 */3 * *" # runs every 3 days at 3 AM UTC jobs: - perf-test: - uses: ./.github/workflows/perfTest-reusable.yml - with: - test_name: performance-test-ncVoters5M - input_file: "zingg_community_performance_reports/perf_test/perfTestInput_ncVoters5M.json" - secrets: inherit +  perf-test: +    uses: ./.github/workflows/perfTest-reusable.yml +    with: +      test_name: performance-test-ncVoters5M +      input_file: "zingg_community_performance_reports/perf_test/perfTestInput_ncVoters5M.json" +    secrets: inherit \ No newline at end of file diff --git a/common/core/src/main/java/zingg/common/core/executor/ZinggBaseCommon.java b/common/core/src/main/java/zingg/common/core/executor/ZinggBaseCommon.java index 3dc8689db..480d4d27e 100644 --- a/common/core/src/main/java/zingg/common/core/executor/ZinggBaseCommon.java +++ b/common/core/src/main/java/zingg/common/core/executor/ZinggBaseCommon.java @@ -63,6 +63,8 @@ public void setSession(S s) { public abstract IZArgs getArgs(); public abstract void setArgs(IZArgs a); + + public abstract void track(boolean collectMetrics); public void postMetrics() { boolean collectMetrics = getArgs().getCollectMetrics(); @@ -80,6 +82,8 @@ public void postMetrics() { Analytics.trackPropValue(Metric.OS_ARCH, collectMetrics); Analytics.trackPropValue(Metric.OS_NAME, collectMetrics); Analytics.trackDomain(Metric.DOMAIN, collectMetrics); + + track(collectMetrics); // populates dataFormat, outputFormat, numTotalFields, etc. Analytics.postEvent(zinggOption.getName(), collectMetrics); } From 91f8bb419445e6c5e9c99bef2b7244a6ef0eb7bf Mon Sep 17 00:00:00 2001 From: chandan Date: Wed, 5 Aug 2026 17:00:50 +0530 Subject: [PATCH 011/144] Fix labeler truncating field values in vertical display The labeler rendered pairs via ZFrame.show(int), which lets Spark default to truncate=true, capping cell values at 20 characters (e.g. emails shown as john_smith@cas...). Add a show(int, boolean) overload to ZFrame and SparkFrame, and have VerticalDisplayUtility call show(MAX_COLUMNS, false) so full values are displayed. --- common/client/src/main/java/zingg/common/client/ZFrame.java | 1 + .../common/client/util/vertical/VerticalDisplayUtility.java | 2 +- spark/client/src/main/java/zingg/spark/client/SparkFrame.java | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/common/client/src/main/java/zingg/common/client/ZFrame.java b/common/client/src/main/java/zingg/common/client/ZFrame.java index 7a0506902..aa4f9d2a3 100644 --- a/common/client/src/main/java/zingg/common/client/ZFrame.java +++ b/common/client/src/main/java/zingg/common/client/ZFrame.java @@ -120,6 +120,7 @@ public interface ZFrame { public C or(C col1, C col2); public void show(int num); + public void show(int num, boolean truncate); public void show(); public String showSchema(); diff --git a/common/client/src/main/java/zingg/common/client/util/vertical/VerticalDisplayUtility.java b/common/client/src/main/java/zingg/common/client/util/vertical/VerticalDisplayUtility.java index ab3739d82..0106fde93 100644 --- a/common/client/src/main/java/zingg/common/client/util/vertical/VerticalDisplayUtility.java +++ b/common/client/src/main/java/zingg/common/client/util/vertical/VerticalDisplayUtility.java @@ -18,7 +18,7 @@ public VerticalDisplayUtility(DFObjectUtil dfObjectUtil) { public void showVertical(ZFrame zFrame) throws ZinggClientException { ZFrame verticalZFrame = convertVertical(zFrame); - verticalZFrame.show(MAX_COLUMNS); + verticalZFrame.show(MAX_COLUMNS, false); } public ZFrame convertVertical(ZFrame zFrame) throws ZinggClientException { diff --git a/spark/client/src/main/java/zingg/spark/client/SparkFrame.java b/spark/client/src/main/java/zingg/spark/client/SparkFrame.java index 3f47a540f..2056b2346 100644 --- a/spark/client/src/main/java/zingg/spark/client/SparkFrame.java +++ b/spark/client/src/main/java/zingg/spark/client/SparkFrame.java @@ -312,6 +312,10 @@ public void show(int num) { df.show(num); } + public void show(int num, boolean truncate) { + df.show(num, truncate); + } + public void show() { df.show(); } From 256f8240be61c045054fd7bf8c4107867015ece8 Mon Sep 17 00:00:00 2001 From: Sonal Date: Tue, 11 Aug 2026 09:13:29 +0530 Subject: [PATCH 012/144] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8959a4f7f..0e2d7f159 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![performance-test-febrl120K](https://github.com/zinggAI/zingg/actions/workflows/perfTest-febrl120K.yml/badge.svg)](https://github.com/zinggAI/zingg/actions/workflows/perfTest-febrl120K.yml) [![performance-test-ncVoters5M](https://github.com/zinggAI/zingg/actions/workflows/perfTest-ncVoters5M.yml/badge.svg)](https://github.com/zinggAI/zingg/actions/workflows/perfTest-ncVoters5M.yml) -## 0.6.0 release is out! +## 0.7.0 release is out! ## The Problem From 19c1ff925f28499e35b55b031ba57133b74936e3 Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Tue, 11 Aug 2026 14:39:43 +0530 Subject: [PATCH 013/144] docs: fix typos and grammar in FAQ Correct minor typos and grammar in docs/faq.md: add missing apostrophe, replace 'advance/peristent/gden' with 'advanced/persistent/golden', improve CDP paragraph wording and hyphenate 'out-of-the-box'. Minor copy edits for clarity and readability. --- docs/faq.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index fdd3a2bbe..3dabbd728 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -6,7 +6,7 @@ nav_order: 9 ## How much training is enough? -Typically 30-40 positive pairs (matches) should build a good first model. While marking records through the interactive learner, you can check Zingg's predictions for the shown pair. If they seem to be correct, you can pause and run Zingg in train and match phases to see what result you are getting. If not satisfied, you can always run the findTrainingData and label jobs again and they will pick from the last training round. +Typically, 30-40 positive pairs (matches) should build a good first model. While marking records through the interactive learner, you can check Zingg's predictions for the shown pair. If they seem to be correct, you can pause and run Zingg in train and match phases to see what result you are getting. If not satisfied, you can always run the findTrainingData and label jobs again and they will pick from the last training round. ## Do I need to train for every new dataset? @@ -16,19 +16,19 @@ No, absolutely not! Train only if the schema(attributes or their types) has chan Depends on the data size you have. Check [hardware sizing](setup/hardwareSizing.md) for more details. -## I dont have much background in ML or Spark. Can I still use Zingg ? +## I don't have much background in ML or Spark. Can I still use Zingg ? Very much! Zingg uses Spark and ML under the hood so that you don't have to worry about the rules and the scale. ## Is Zingg an MDM? -Zingg Community Version is a DIY Master Data Management Product, solving the entity matching and linking process. You can build an MDM in a data store of your choice using Zingg Community. Zingg Enterprise Version is a lakehouse/warehouse native MDM with advance features for globally unique and peristent identifier, gden records, agentic stewardship and survivorship. +Zingg Community Version is a DIY Master Data Management Product, solving the entity matching and linking process. You can build an MDM in a data store of your choice using Zingg Community. Zingg Enterprise Version is a lakehouse/warehouse native MDM with advanced features for globally unique and persistent identifiers, golden records, agentic stewardship and survivorship. ## Is Zingg a CDP ? -No, Zingg is not a CDP. However, Zingg is part of the Composable CDP stack, where enterprises assemble customer platform off your warehouse or datalake, Zingg is a great fit. You can leverage existing ETL, observability, and other tools which are part of your data stack and use Zingg for identity.\ -Zingg Community Version can be used to build a composable CDP by identity resolution natively on the warehouse and datalake and building customer 360 views. Zingg's identity resolution is far more powerful than what is offered by any out of the box CDP.\ -Zingg Enterprise's probabilistic and deterministic matching take this further beyond. Here is an [article](https://hightouch.com/blog/warehouse-identity-resolution/) describing how you can build your own CDP on the warehouse with Zingg. +No, Zingg is not a CDP. However, Zingg is part of the Composable CDP stack, where enterprises assemble a customer platform off their warehouse or datalake, Zingg is a great fit. You can leverage existing ETL, observability, and other tools which are part of your data stack and use Zingg for identity.\ +Zingg Community Version can be used to build a composable CDP by identity resolution natively on the warehouse and datalake and building customer 360 views. Zingg's identity resolution is far more powerful than what is offered by any out-of-the-box CDP.\ +Zingg Enterprise's probabilistic and deterministic matching takes this further. Here is an [article](https://hightouch.com/blog/warehouse-identity-resolution/) describing how you can build your own CDP on the warehouse with Zingg. ## I can do Entity Resolution using a graph database like TigerGraph/Neo4J, why do I need Zingg ? From 8b85ed81cf9a200ba27d3dab062ca51e31b9785b Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Tue, 11 Aug 2026 15:12:27 +0530 Subject: [PATCH 014/144] Migrate docs to new GitBook structure Reorganize documentation --- docs/.gitbook/tags.yaml | 3 + docs/README.md | 31 +- docs/SUMMARY.md | 229 +++--- docs/assembly/dependency-reduced-pom.xml | 203 +++++ .../connect-cloud-storage.md | 311 +++++++ .../connect-cloud-warehouses/README.md | 19 + .../connect-azure-databricks.md | 125 +++ .../connect-bigquery.md | 82 ++ .../connect-exasol.md | 78 ++ .../connect-microsoft-fabric.md | 109 +++ .../connect-redshift.md | 3 + .../connect-snowflake.md | 72 ++ .../connect-your-data/connect-file-formats.md | 244 ++++++ .../connect-graph-databases-neo4j.md | 42 + .../connect-nosql-databases.md | 102 +++ .../connect-relational-databases.md | 131 +++ .../pipes-and-data-connections.md | 159 ++++ docs/contributing/contributing-to-zingg.md | 22 + .../README.md | 14 + .../macos-setup-guide.md | 164 ++++ .../ubuntu-wsl2-setup-guide.md | 206 +++++ .../frequently-asked-questions.md | 126 +++ .../explain-a-specific-cluster.md | 105 +++ docs/interpreting-results/explain-matches.md | 33 + .../interpret-output-scores.md | 169 ++++ .../interpreting-results/output-statistics.md | 157 ++++ .../platform-guide-for-aws-emr.md | 22 + .../platform-guide-for-aws-glue.md | 672 +++++++++++++++ .../platform-guide-for-azure-databricks.md | 773 ++++++++++++++++++ .../platform-guide-for-gcp-dataproc.md | 486 +++++++++++ .../platform-guide-for-microsoft-fabric.md | 481 +++++++++++ .../platform-guide-for-snowflake.md | 350 ++++++++ .../combine-match-models.md | 133 +++ .../identity-rag-with-zingg-and-langchain.md | 232 ++++++ .../pre-trained-models.md | 137 ++++ docs/reference/cli-command-reference.md | 117 +++ docs/reference/configuration-schema.md | 98 +++ .../hardware-sizing-and-benchmarks.md | 43 + docs/reference/reading-material.md | 27 + docs/reference/runtime-properties.md | 102 +++ docs/reference/zingg-command-line.md | 209 +++++ .../running-zingg/build-and-save-the-model.md | 72 ++ docs/running-zingg/cluster-approval.md | 45 + docs/running-zingg/compare-model-results.md | 132 +++ docs/running-zingg/configure-zingg.md | 487 +++++++++++ docs/running-zingg/create-training-data.md | 108 +++ docs/running-zingg/experience-zingg.md | 115 +++ .../generate-model-documentation.md | 73 ++ docs/running-zingg/install-zingg.md | 342 ++++++++ docs/running-zingg/knowledge-graph.md | 46 ++ docs/running-zingg/label-training-pairs.md | 108 +++ docs/running-zingg/link-across-datasets.md | 121 +++ docs/running-zingg/lookup-data.md | 67 ++ docs/running-zingg/quick-start-docker.md | 423 ++++++++++ docs/running-zingg/reassign-zingg-id.md | 228 ++++++ .../running-zingg/run-incremental-matching.md | 231 ++++++ docs/running-zingg/run-the-match-phase.md | 119 +++ docs/running-zingg/step-by-step-guide.md | 135 +++ docs/running-zingg/verify-blocking.md | 96 +++ .../security-and-privacy.md | 51 ++ .../telemetry-and-usage-metrics.md | 71 ++ .../tuning/configure-field-standardization.md | 135 +++ docs/tuning/custom-blocking-and-similarity.md | 68 ++ docs/tuning/improve-accuracy/README.md | 60 ++ .../remove-stopwords-optional.md | 86 ++ .../community-vs-enterprise/README.md | 91 +++ ...unity-to-enterprise-content-to-be-added.md | 8 + docs/zingg-concepts/concept-glossary.md | 374 +++++++++ .../entity-resolution/README.md | 223 +++++ ...deterministic-vs-probabilistic-matching.md | 63 ++ .../zingg-concepts/how-zingg-learns/README.md | 114 +++ .../how-zingg-learns/match-types/README.md | 108 +++ .../match-types/dont_use-match.md | 120 +++ .../match-types/email-match.md | 120 +++ .../match-types/email_optimised-match.md | 92 +++ .../match-types/exact-match.md | 148 ++++ .../match-types/fuzzy-match.md | 168 ++++ .../match-types/fuzzy_optimised-match.md | 112 +++ .../match-types/mapping_-filename-match.md | 127 +++ .../match-types/null_or_blank-match.md | 117 +++ .../match-types/numeric-match.md | 115 +++ .../match-types/numeric_with_units-match.md | 94 +++ .../match-types/only_alphabets_exact-match.md | 96 +++ .../match-types/only_alphabets_fuzzy-match.md | 104 +++ .../only_alphabets_fuzzy_optimised-match.md | 72 ++ .../match-types/pincode-match.md | 97 +++ .../match-types/text-match.md | 115 +++ .../how-zingg-learns/zingg-models/README.md | 87 ++ .../zingg-models/blocking-model.md | 86 ++ .../zingg-models/similarity-model.md | 73 ++ .../standardize-fields-and-results.md | 66 ++ docs/zingg-concepts/identity-graph.md | 53 ++ docs/zingg-concepts/pass-through.md | 104 +++ docs/zingg-concepts/z-cluster-and-zingg-id.md | 69 ++ docs/zingg-python-api/community-python-api.md | 274 +++++++ .../enterprise-zinggec-python-api.md | 489 +++++++++++ .../enterprise-zingges-python-api.md | 160 ++++ docs/zingg-python-api/working-with-python.md | 45 + 98 files changed, 14305 insertions(+), 119 deletions(-) create mode 100644 docs/.gitbook/tags.yaml create mode 100644 docs/assembly/dependency-reduced-pom.xml create mode 100644 docs/connect-your-data/connect-cloud-storage.md create mode 100644 docs/connect-your-data/connect-cloud-warehouses/README.md create mode 100644 docs/connect-your-data/connect-cloud-warehouses/connect-azure-databricks.md create mode 100644 docs/connect-your-data/connect-cloud-warehouses/connect-bigquery.md create mode 100644 docs/connect-your-data/connect-cloud-warehouses/connect-exasol.md create mode 100644 docs/connect-your-data/connect-cloud-warehouses/connect-microsoft-fabric.md create mode 100644 docs/connect-your-data/connect-cloud-warehouses/connect-redshift.md create mode 100644 docs/connect-your-data/connect-cloud-warehouses/connect-snowflake.md create mode 100644 docs/connect-your-data/connect-file-formats.md create mode 100644 docs/connect-your-data/connect-graph-databases-neo4j.md create mode 100644 docs/connect-your-data/connect-nosql-databases.md create mode 100644 docs/connect-your-data/connect-relational-databases.md create mode 100644 docs/connect-your-data/pipes-and-data-connections.md create mode 100644 docs/contributing/contributing-to-zingg.md create mode 100644 docs/contributing/setting-up-zingg-development-environment/README.md create mode 100644 docs/contributing/setting-up-zingg-development-environment/macos-setup-guide.md create mode 100644 docs/contributing/setting-up-zingg-development-environment/ubuntu-wsl2-setup-guide.md create mode 100644 docs/frequently-asked-questions/frequently-asked-questions.md create mode 100644 docs/interpreting-results/explain-a-specific-cluster.md create mode 100644 docs/interpreting-results/explain-matches.md create mode 100644 docs/interpreting-results/interpret-output-scores.md create mode 100644 docs/interpreting-results/output-statistics.md create mode 100644 docs/platform-guides/platform-guide-for-aws-emr.md create mode 100644 docs/platform-guides/platform-guide-for-aws-glue.md create mode 100644 docs/platform-guides/platform-guide-for-azure-databricks.md create mode 100644 docs/platform-guides/platform-guide-for-gcp-dataproc.md create mode 100644 docs/platform-guides/platform-guide-for-microsoft-fabric.md create mode 100644 docs/platform-guides/platform-guide-for-snowflake.md create mode 100644 docs/recipes-and-integration/combine-match-models.md create mode 100644 docs/recipes-and-integration/identity-rag-with-zingg-and-langchain.md create mode 100644 docs/recipes-and-integration/pre-trained-models.md create mode 100644 docs/reference/cli-command-reference.md create mode 100644 docs/reference/configuration-schema.md create mode 100644 docs/reference/hardware-sizing-and-benchmarks.md create mode 100644 docs/reference/reading-material.md create mode 100644 docs/reference/runtime-properties.md create mode 100644 docs/reference/zingg-command-line.md create mode 100644 docs/running-zingg/build-and-save-the-model.md create mode 100644 docs/running-zingg/cluster-approval.md create mode 100644 docs/running-zingg/compare-model-results.md create mode 100644 docs/running-zingg/configure-zingg.md create mode 100644 docs/running-zingg/create-training-data.md create mode 100644 docs/running-zingg/experience-zingg.md create mode 100644 docs/running-zingg/generate-model-documentation.md create mode 100644 docs/running-zingg/install-zingg.md create mode 100644 docs/running-zingg/knowledge-graph.md create mode 100644 docs/running-zingg/label-training-pairs.md create mode 100644 docs/running-zingg/link-across-datasets.md create mode 100644 docs/running-zingg/lookup-data.md create mode 100644 docs/running-zingg/quick-start-docker.md create mode 100644 docs/running-zingg/reassign-zingg-id.md create mode 100644 docs/running-zingg/run-incremental-matching.md create mode 100644 docs/running-zingg/run-the-match-phase.md create mode 100644 docs/running-zingg/step-by-step-guide.md create mode 100644 docs/running-zingg/verify-blocking.md create mode 100644 docs/security-and-privacy/security-and-privacy.md create mode 100644 docs/security-and-privacy/telemetry-and-usage-metrics.md create mode 100644 docs/tuning/configure-field-standardization.md create mode 100644 docs/tuning/custom-blocking-and-similarity.md create mode 100644 docs/tuning/improve-accuracy/README.md create mode 100644 docs/tuning/improve-accuracy/remove-stopwords-optional.md create mode 100644 docs/zingg-concepts/community-vs-enterprise/README.md create mode 100644 docs/zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md create mode 100644 docs/zingg-concepts/concept-glossary.md create mode 100644 docs/zingg-concepts/entity-resolution/README.md create mode 100644 docs/zingg-concepts/entity-resolution/deterministic-vs-probabilistic-matching.md create mode 100644 docs/zingg-concepts/how-zingg-learns/README.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/README.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/dont_use-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/email-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_exact-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/match-types/text-match.md create mode 100644 docs/zingg-concepts/how-zingg-learns/zingg-models/README.md create mode 100644 docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md create mode 100644 docs/zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md create mode 100644 docs/zingg-concepts/how-zingg-learns/zingg-models/standardize-fields-and-results.md create mode 100644 docs/zingg-concepts/identity-graph.md create mode 100644 docs/zingg-concepts/pass-through.md create mode 100644 docs/zingg-concepts/z-cluster-and-zingg-id.md create mode 100644 docs/zingg-python-api/community-python-api.md create mode 100644 docs/zingg-python-api/enterprise-zinggec-python-api.md create mode 100644 docs/zingg-python-api/enterprise-zingges-python-api.md create mode 100644 docs/zingg-python-api/working-with-python.md diff --git a/docs/.gitbook/tags.yaml b/docs/.gitbook/tags.yaml new file mode 100644 index 000000000..061d23375 --- /dev/null +++ b/docs/.gitbook/tags.yaml @@ -0,0 +1,3 @@ +- tag: enterprise-only + label: Enterprise only + color: $primary diff --git a/docs/README.md b/docs/README.md index 7ea1736b7..67cdb3786 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,21 @@ --- -description: Hope you find us useful :-) +description: >- + ML-powered entity resolution and record matching - built for your warehouse, + at any scale. --- -# Welcome To Zingg +# What is Zingg -This is the latest documentation for Zingg. Release wise documentation can be accessed through: +Most teams don't have a duplication problem; they have an entity resolution problem. The same entity lives in five systems under three different names. Every downstream decision built on that data becomes unreliable: analytics produce conflicting numbers, compliance checks miss connections, and operational teams work from different versions of the same record. When AI agents are in the loop, the problem compounds; an agent doesn't know a record is a duplicate, so it acts on each one independently, multiplying errors at the speed of automation. -* [v0.6.0](https://docs.zingg.ai/0.6.0/) -* [v0.5.0](https://app.gitbook.com/o/kn0G4kXLdlfPagjso48S/s/LzEhax5IUbfFVRkRns9Q/) -* [v0.4.0](https://app.gitbook.com/o/kn0G4kXLdlfPagjso48S/s/a7sgpR3odgfck5L8KMcN/) -* [v0.3.4](https://app.gitbook.com/o/kn0G4kXLdlfPagjso48S/s/ngqsuC2LVWwrOiyPZbU2/) -* [v0.3.3](https://app.gitbook.com/o/kn0G4kXLdlfPagjso48S/s/1ZRr4ik7PJ2qmCP10In9/) +Solving this at scale is harder than it looks. The number of comparisons grows quadratically with record count, and rule-based approaches break down on real-world data variation. -## Why? +Zingg solves this with ML-powered entity resolution that runs directly on your infrastructure on Spark or Snowflake, so your data never leaves your premises. You label a small set of example pairs to teach Zingg what a match looks like for your data. Zingg learns from those labels, adapts as it sees more variation, and scales that judgment across millions of records, giving your pipelines and agents a resolved, trustworthy view of every entity to act on. -Data silos hurt all business functions - customer analytics, supplier consolidation, risk and compliance, and sales and marketing. +Whether you're resolving customers, patients, suppliers, citizens, or product issues—Zingg works on any entity type. -![Data Silos](.gitbook/assets/dataSilos.png) +### What do you want to do today? -Zingg is a quick and scalable way to build a single source of truth for core business entities. With Zingg, the analytics engineer and the data scientist can quickly integrate data silos and build unified views at scale! - -![# Zingg - Data Mastering At Scale with ML](.gitbook/assets/dataMastering.png) - -
- -## Ask For Help - -If you want to understand if Zingg is a fit for your use case or have other questions or suggestions, join our [Slack.](https://join.slack.com/t/zinggai/shared_invite/zt-w7zlcnol-vEuqU9m~Q56kLLUVxRgpOA) +
What is entity resolutionentity-resolutionUnderstand the problem Zingg solves, why fragmented entity data breaks downstream systems, and why rule-based approaches fail at scale.
Run Zingg for the first timeRunning ZinggGet Zingg running on your platform and see your first match result in under 30 minutes.
Connect your dataConnect Your DataConfigure Zingg pipes for your data source - cloud warehouses, cloud storage, databases, and file formats.
Build and train a modelbuild-and-save-the-model.mdConfigure your data, label training pairs, and build a model for your dataset.
Interpret and tune resultsTuningUnderstand your match output, set thresholds, and improve accuracy.
ReferencesReferenceConfiguration schema, CLI commands, runtime properties, and FAQ.
+{% embed url="https://www.zingg.ai/company/contact/contact" %} diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 8584c3ce3..6f6d26141 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,101 +1,132 @@ # Table of contents -* [Welcome To Zingg](README.md) -* [Step-By-Step Guide](stepByStep.md) - * [Installation](setup/installation.md) - * [Docker](stepbystep/installation/docker/README.md) - * [Sharing Custom Data And Config Files](stepbystep/installation/docker/sharing-custom-data-and-config-files.md) - * [Shared Locations](stepbystep/installation/docker/shared-locations.md) - * [File Read/Write Permissions](stepbystep/installation/docker/file-read-write-permissions.md) - * [Copying Files To And From The Container](stepbystep/installation/docker/copying-files-to-and-from-the-container.md) - * [Installing From Release](stepbystep/installation/installing-from-release/README.md) - * [Single Machine Setup](stepbystep/installation/installing-from-release/single-machine-setup.md) - * [Spark Cluster Checklist](stepbystep/installation/installing-from-release/spark-cluster-checklist.md) - * [Installing Zingg](stepbystep/installation/installing-from-release/installing-zingg.md) - * [Verifying The Installation](stepbystep/installation/installing-from-release/verification.md) - * [Enterprise Installation for Snowflake](stepbystep/installation/installing-zingg-enterprise-snowflake/README.md) - * [Setting up Zingg](stepbystep/installation/installing-zingg-enterprise-snowflake/setting-up-zingg-enterprise-for-snowflake.md) - * [Snowflake Properties](stepbystep/installation/installing-zingg-enterprise-snowflake/snowflake-properties.md) - * [Match Configuration](stepbystep/installation/installing-zingg-enterprise-snowflake/match-configuration.md) - * [Running Asynchronously](stepbystep/installation/installing-zingg-enterprise-snowflake/running-asynchronously.md) - * [Verifying The Installation](stepbystep/installation/installing-zingg-enterprise-snowflake/verifying-the-installation.md) - * [Compiling From Source](stepbystep/installation/compiling-from-source.md) - * [Hardware Sizing](setup/hardwareSizing.md) - * [Zingg Runtime Properties](stepbystep/zingg-runtime-properties.md) - * [Zingg Command Line](stepbystep/zingg-command-line.md) - * [Configuration](stepbystep/configuration/README.md) - * [Configuring Through Environment Variables](stepbystep/configuration/configuring-through-environment-variables.md) - * [Data Input And Output](stepbystep/configuration/data-input-and-output/README.md) - * [Input Data](stepbystep/configuration/data-input-and-output/data.md) - * [Output](stepbystep/configuration/data-input-and-output/output.md) - * [Field Definitions](stepbystep/configuration/field-definitions.md) - * [Standardise Postprocessor](StandardisePostprocessor.md) - * [User Defined Mapping Match Types](stepbystep/configuration/adv-matchtypes.md) - * [Deterministic Matching](deterministicMatching.md) - * [Pass Through Data](passthru.md) - * [Model Location](stepbystep/configuration/model-location.md) - * [Tuning Label, Match And Link Jobs](stepbystep/configuration/tuning-label-match-and-link-jobs.md) - * [Telemetry](stepbystep/configuration/telemetry.md) - * [Working With Training Data](setup/training/createTrainingData.md) - * [Finding Records For Training Set Creation](setup/training/findTrainingData.md) - * [Labeling Records](setup/training/label.md) - * [Find And Label](setup/training/findAndLabel.md) - * [Using Pre-existing Training Data](setup/training/addOwnTrainingData.md) - * [Updating Labeled Pairs](updatingLabels.md) - * [Documenting The Training Data](stepbystep/createtrainingdata/generatingdocumentation.md) - * [Model Difference](stepbystep/createtrainingdata/modeldiff.md) - * [Ensuring Scalability](verifyBlocking.md) - * [Building And Saving The Model](setup/train.md) - * [Finding The Matches](setup/match.md) - * [Adding Incremental Data](runIncremental.md) - * [Linking Across Datasets](setup/link.md) - * [Lookup Data](lookup.md) - * [Approval of Clusters](approval.md) - * [Reassign ZINGG ID](reassignZinggId.md) -* [Data Sources and Sinks](dataSourcesAndSinks/connectors.md) - * [Zingg Pipes](dataSourcesAndSinks/pipes.md) - * [Databricks](dataSourcesAndSinks/databricks.md) - * [Microsoft Fabric](dataSourcesAndSinks/fabric.md) - * [Snowflake](dataSourcesAndSinks/snowflake.md) - * [JDBC](dataSourcesAndSinks/jdbc.md) - * [Postgres](connectors/jdbc/postgres.md) - * [MySQL](connectors/jdbc/mysql.md) - * [AWS S3](dataSourcesAndSinks/amazonS3.md) - * [Cassandra](dataSourcesAndSinks/cassandra.md) - * [MongoDB](dataSourcesAndSinks/mongodb.md) - * [Neo4j](dataSourcesAndSinks/neo4j.md) - * [Parquet](dataSourcesAndSinks/parquet.md) - * [BigQuery](dataSourcesAndSinks/bigquery.md) - * [Exasol](dataSourcesAndSinks/exasol.md) -* [Working With Python](working-with-python.md) - * [Community Python API](pythonOss/markdown/index.md) - * [Enterprise Python API](working-with-python-enterprise.md) - * [ZinggEC Python API](pythonEC/markdown/index.md) - * [ZinggES Python API](pythonES/markdown/index.md) -* [Running Zingg On Cloud](running/running.md) - * [Running On AWS](running/aws.md) - * [Running On Azure](running/azure.md) - * [Running On Databricks](running/databricks.md) - * [Running on Fabric](running/running-on-fabric.md) -* [Zingg Models](zModels.md) - * [Pre-Trained Models](pretrainedModels.md) -* [Improving Accuracy](improving-accuracy/README.md) - * [Ignoring Commonly Occuring Words While Matching](accuracy/stopWordsRemoval.md) - * [Defining Domain Specific Blocking And Similarity Functions](accuracy/definingOwn.md) -* [Interpreting Output Scores](scoring.md) -* [Explanation of Matches](explainoutput/README.md) - * [Which records matched with each other in a cluster](explainoutput/which-records-matched-with-each-other-in-a-cluster.md) - * [Output Statistics](explainoutput/stats/README.md) - * [Configuring Output Statistics](explainoutput/stats/configuring-output-statistics.md) - * [Cluster Statistics](explainoutput/stats/cluster-statistics.md) - * [Record Statistics](explainoutput/stats/record-statistics.md) - * [Summary Statistics](explainoutput/stats/summary-statistics.md) -* [Combining Different Match Models](relations.md) -* [Reporting Bugs And Contributing](contributing.md) - * [Setting Up Zingg Development Environment](settingUpZingg.md) - * [macOS Setup Guide](macOSSetup.md) - * [Ubuntu/WSL2 Setup Guide](ubuntuSetup.md) -* [Community](community.md) -* [Frequently Asked Questions](faq.md) -* [Reading Material](reading.md) -* [Security And Privacy](security.md) +## Zingg Concepts + +* [What is Zingg](README.md) +* [Entity Resolution](zingg-concepts/entity-resolution/README.md) + * [Deterministic vs Probabilistic Matching](zingg-concepts/entity-resolution/deterministic-vs-probabilistic-matching.md) +* [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) + * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) +* [How Zingg Learns](zingg-concepts/how-zingg-learns/README.md) + * [Zingg Models](zingg-concepts/how-zingg-learns/zingg-models/README.md) + * [Blocking Model](zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) + * [Similarity Model](zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md) + * [Standardize Fields and Results](zingg-concepts/how-zingg-learns/zingg-models/standardize-fields-and-results.md) + * [Match Types](zingg-concepts/how-zingg-learns/match-types/README.md) + * [FUZZY Match](zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md) + * [FUZZY\_OPTIMISED Match](zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md) + * [EXACT Match](zingg-concepts/how-zingg-learns/match-types/exact-match.md) + * [EMAIL Match](zingg-concepts/how-zingg-learns/match-types/email-match.md) + * [EMAIL\_OPTIMISED Match](zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md) + * [PINCODE Match](zingg-concepts/how-zingg-learns/match-types/pincode-match.md) + * [NUMERIC Match](zingg-concepts/how-zingg-learns/match-types/numeric-match.md) + * [NUMERIC\_WITH\_UNITS Match](zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md) + * [TEXT Match](zingg-concepts/how-zingg-learns/match-types/text-match.md) + * [ONLY\_ALPHABETS\_EXACT Match](zingg-concepts/how-zingg-learns/match-types/only_alphabets_exact-match.md) + * [ONLY\_ALPHABETS\_FUZZY Match](zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy-match.md) + * [ONLY\_ALPHABETS\_FUZZY\_OPTIMISED Match](zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md) + * [NULL\_OR\_BLANK Match](zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md) + * [DONT\_USE Match](zingg-concepts/how-zingg-learns/match-types/dont_use-match.md) + * [MAPPING\_(FILENAME) Match](zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md) +* [Identity Graph](zingg-concepts/identity-graph.md) +* [Z Cluster and Zingg ID](zingg-concepts/z-cluster-and-zingg-id.md) +* [Pass Through](zingg-concepts/pass-through.md) +* [Concept Glossary](zingg-concepts/concept-glossary.md) + +## Running Zingg + +* [Step-by-Step Guide](running-zingg/step-by-step-guide.md) +* [Experience Zingg](running-zingg/experience-zingg.md) +* [Quick Start (Docker)](running-zingg/quick-start-docker.md) +* [Install Zingg](running-zingg/install-zingg.md) +* [Configure Zingg](running-zingg/configure-zingg.md) +* [Create Training Data](running-zingg/create-training-data.md) +* [Label Training Pairs](running-zingg/label-training-pairs.md) +* [Verify Blocking](running-zingg/verify-blocking.md) +* [Generate Model Documentation](running-zingg/generate-model-documentation.md) +* [Build and Save the Model](running-zingg/build-and-save-the-model.md) +* [Run the match phase](running-zingg/run-the-match-phase.md) +* [Link across Datasets](running-zingg/link-across-datasets.md) +* [Run Incremental Matching](running-zingg/run-incremental-matching.md) +* [Reassign Zingg ID](running-zingg/reassign-zingg-id.md) +* [Lookup Data](running-zingg/lookup-data.md) +* [Cluster Approval](running-zingg/cluster-approval.md) +* [Compare Model Results](running-zingg/compare-model-results.md) +* [Knowledge Graph](running-zingg/knowledge-graph.md) + +## Connect Your Data + +* [Pipes and Data Connections](connect-your-data/pipes-and-data-connections.md) +* [Connect Cloud Warehouses](connect-your-data/connect-cloud-warehouses/README.md) + * [Connect Azure Databricks](connect-your-data/connect-cloud-warehouses/connect-azure-databricks.md) + * [Connect Snowflake](connect-your-data/connect-cloud-warehouses/connect-snowflake.md) + * [Connect BigQuery](connect-your-data/connect-cloud-warehouses/connect-bigquery.md) + * [Connect Redshift](connect-your-data/connect-cloud-warehouses/connect-redshift.md) + * [Connect Microsoft Fabric](connect-your-data/connect-cloud-warehouses/connect-microsoft-fabric.md) + * [Connect Exasol](connect-your-data/connect-cloud-warehouses/connect-exasol.md) +* [Connect Cloud Storage](connect-your-data/connect-cloud-storage.md) +* [Connect File Formats](connect-your-data/connect-file-formats.md) +* [Connect Relational Databases](connect-your-data/connect-relational-databases.md) +* [Connect NoSQL Databases](connect-your-data/connect-nosql-databases.md) +* [Connect Graph Databases (Neo4j)](connect-your-data/connect-graph-databases-neo4j.md) + +## Zingg Python API + +* [Working with Python](zingg-python-api/working-with-python.md) +* [Community Python API](zingg-python-api/community-python-api.md) +* [Enterprise ZinggEC Python API](zingg-python-api/enterprise-zinggec-python-api.md) +* [Enterprise ZinggES Python API](zingg-python-api/enterprise-zingges-python-api.md) + +## Tuning + +* [Configure Field Standardization](tuning/configure-field-standardization.md) +* [Improve Accuracy](tuning/improve-accuracy/README.md) + * [Remove Stopwords (Optional)](tuning/improve-accuracy/remove-stopwords-optional.md) +* [Custom Blocking and Similarity](tuning/custom-blocking-and-similarity.md) + +## Interpreting Results + +* [Interpret Output Scores](interpreting-results/interpret-output-scores.md) +* [Explain Matches](interpreting-results/explain-matches.md) +* [Explain a Specific Cluster](interpreting-results/explain-a-specific-cluster.md) +* [Output Statistics](interpreting-results/output-statistics.md) + +## Platform Guides + +* [Platform Guide for Azure Databricks](platform-guides/platform-guide-for-azure-databricks.md) +* [Platform Guide for Microsoft Fabric](platform-guides/platform-guide-for-microsoft-fabric.md) +* [Platform Guide for AWS EMR](platform-guides/platform-guide-for-aws-emr.md) +* [Platform Guide for AWS GLUE](platform-guides/platform-guide-for-aws-glue.md) +* [Platform Guide for GCP Dataproc](platform-guides/platform-guide-for-gcp-dataproc.md) +* [Platform Guide for Snowflake](platform-guides/platform-guide-for-snowflake.md) + +## Recipes and Integration + +* [Identity RAG with Zingg and LangChain](recipes-and-integration/identity-rag-with-zingg-and-langchain.md) +* [Combine Match Models](recipes-and-integration/combine-match-models.md) +* [Pre-trained Models](recipes-and-integration/pre-trained-models.md) + +## Reference + +* [Configuration Schema](reference/configuration-schema.md) +* [Zingg Command Line](reference/zingg-command-line.md) +* [CLI Command Reference](reference/cli-command-reference.md) +* [Runtime Properties](reference/runtime-properties.md) +* [Hardware Sizing and Benchmarks](reference/hardware-sizing-and-benchmarks.md) +* [Reading Material](reference/reading-material.md) + +## Contributing + +* [Contributing to Zingg](contributing/contributing-to-zingg.md) +* [Setting Up Zingg Development Environment](contributing/setting-up-zingg-development-environment/README.md) + * [macOS Setup Guide](contributing/setting-up-zingg-development-environment/macos-setup-guide.md) + * [Ubuntu/WSL2 Setup Guide](contributing/setting-up-zingg-development-environment/ubuntu-wsl2-setup-guide.md) + +## Frequently Asked Questions + +* [Frequently Asked Questions](frequently-asked-questions/frequently-asked-questions.md) + +## Security and Privacy + +* [Security and Privacy](security-and-privacy/security-and-privacy.md) +* [Telemetry and Usage Metrics](security-and-privacy/telemetry-and-usage-metrics.md) diff --git a/docs/assembly/dependency-reduced-pom.xml b/docs/assembly/dependency-reduced-pom.xml new file mode 100644 index 000000000..04872f1a3 --- /dev/null +++ b/docs/assembly/dependency-reduced-pom.xml @@ -0,0 +1,203 @@ + + + + parent + zingg + ${zingg.version} + + 4.0.0 + zingg + + + + maven-shade-plugin + ${shade.plugin.version} + + + package + + shade + + + + + + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + DUMMY.SF + DUMMY.DSA + **/*.RSA + *unused*.jar + org.scala-lang:scala-library:jar:2.12.12 + org.codehaus.jackson:jackson-mapper-asl:jar:1.9.13 + com.fasterxml.jackson.core:jackson-core:jar:2.12.0 + com.fasterxml.jackson*:* + org.codehaus.jackson:* + org.apache.httpcomponents:* + org.apache.commons:* + commons-beanutils:* + commons-codec:* + com.thoughtworks.paranamer:* + org.scala-lang:* + sonal + *:jar:* + org.slf4j:* + + + + + + maven-assembly-plugin + 3.6.0 + + + make-assembly + package + + single + + + + + + ${project.basedir}/src/assembly/dist.xml + + gnu + + + + + + + org.mockito + mockito-inline + 5.2.0 + test + + + org.mockito + mockito-core + 5.2.0 + test + + + byte-buddy + net.bytebuddy + + + byte-buddy-agent + net.bytebuddy + + + objenesis + org.objenesis + + + + + com.opencsv + opencsv + 5.12.0 + test + + + commons-beanutils + commons-beanutils + + + commons-lang3 + org.apache.commons + + + commons-text + org.apache.commons + + + commons-collections4 + org.apache.commons + + + + + org.junit.jupiter + junit-jupiter-engine + 5.8.1 + test + + + junit-platform-engine + org.junit.platform + + + apiguardian-api + org.apiguardian + + + + + org.junit.jupiter + junit-jupiter-api + 5.8.1 + test + + + opentest4j + org.opentest4j + + + junit-platform-commons + org.junit.platform + + + apiguardian-api + org.apiguardian + + + + + org.junit.jupiter + junit-jupiter-params + 5.8.1 + test + + + apiguardian-api + org.apiguardian + + + + + org.hamcrest + hamcrest-all + 1.3 + test + + + com.fasterxml.jackson.core + jackson-core + 2.15.2 + compile + + + com.fasterxml.jackson.core + jackson-annotations + 2.15.2 + compile + + + com.fasterxml.jackson.core + jackson-databind + 2.15.2 + compile + + + com.fasterxml.jackson.module + jackson-module-scala_2.12 + 2.15.2 + compile + + + + diff --git a/docs/connect-your-data/connect-cloud-storage.md b/docs/connect-your-data/connect-cloud-storage.md new file mode 100644 index 000000000..e64f73b00 --- /dev/null +++ b/docs/connect-your-data/connect-cloud-storage.md @@ -0,0 +1,311 @@ +--- +description: >- + Configure Zingg to read from and write to cloud object storage including AWS + S3, Azure Blob Storage, and Google Cloud Storage. +--- + +# Connect Cloud Storage + +{% hint style="success" icon="right-long" %} +New to Zingg pipes? Understand how pipes work before configuring them - [Pipes and data connections](pipes-and-data-connections.md). +{% endhint %} + +Zingg can use cloud storage as both a data source and output destination. The JSON config and Python API sections below show how to connect to each storage platform. Available in\ +all editions. + +{% tabs %} +{% tab title="AWS S3" %} +### Prerequisites + +S3 uses the `s3a://` path protocol and requires two Hadoop AWS JARs on the Spark classpath. + +Before connecting, complete these steps: + +1. Create S3 bucket and folder (e.g. bucket: `zingg28032023`, folder: `zingg`) +2. Export credentials. + +```bash +export AWS_ACCESS_KEY_ID = +export AWS_SECRET_ACCESS_KEY = +``` + +If MFA enabled: + +```bash +export AWS_SESSION_TOKEN = +``` + +3. Download Hadoop AWS JARs via Maven and set in `zingg.conf`. + +`spark.jars=//hadoop-aws-3.1.0.jar,`\ +`//aws-java-sdk-bundle-1.11.271.jar` + +### Python API - Community + +```python +from zingg.client import* +from zingg.pipes import* + +schema = "field1 string, field2 string" + +inputPipe = CsvPipe("s3Input", "s3a://your-bucket/path/to/input.csv", schema) +args.setData(inputPipe) + +outputPipe = CsvPipe("s3Output", "s3a://your-bucket/path/to/output") +args.setOutput(outputPipe) +``` + +### Python API - Enterprise + +{% hint style="info" icon="right-long" %} +Enterprise uses `ECsvPipe`. Replace import and class name only - the `s3a://` path pattern stays the same. +{% endhint %} + +```python +from zinggEC.enterprise.common.epipes import* +from zinggEC.enterprise.common.EArguments import* + +schema = "field1 string, field2 string" + +inputPipe = ECsvPipe("s3Input", "s3a://your-bucket/path/to/input.csv", schema) +args.setData(inputPipe) + +outputPipe = ECsvPipe("s3Output", "s3a://your-bucket/path/to/output") +outputPipe.setHeader("true") +args.setOutput(outputPipe) +``` + +### JSON Config + +```json +{ + "data" : [ { + "name" : "s3Input", + "format" : "csv", + "props" : { + "location" : "s3a://your-bucket/path/to/input.csv", + "delimiter" : ",", + "header" : "true" + } + } ], + "output" : [ { + "name" : "s3Output", + "format" : "csv", + "props" : {"location" : "s3a://your-bucket/path/to/output"} + } ] +} +``` + +### Supported file formats + +#### Parquet + +```json +{ + "data" : [ { + "name" : "parquetInput", + "format" : "parquet", + "props" : { + "path" : "s3a://your-bucket/path/to/input.parquet" + } + } ] +} +``` + +#### Avro + +```json +{ + "data" : [ { + "name" : "avroInput", + "format" : "avro", + "props" : { + "path" : "s3a://your-bucket/path/to/input.avro" + } + } ] +} +``` + +#### JSON + +```json +{ + "data" : [ { + "name" : "jsonInput", + "format" : "json", + "props" : { + "path" : "s3a://your-bucket/path/to/input.json" + } + } ] +} +``` + +#### CLI commands + +```bash +./ scripts / zingg.sh-- phase findTrainingData-- properties - file config / \ +zingg.conf-- conf examples / febrl / config .json-- zinggDir s3a : // \ +zingg28032023/zingg + +./ scripts / zingg.sh-- phase match-- properties - file config / zingg.conf-- \ +conf examples / febrl / config.json-- zinggDir s3a: // zingg28032023/zingg +``` + +{% hint style="success" icon="right-long" %} +Setting `zinggDir` to an S3 path stores all Zingg model files and training data in S3. Models are saved at: `your-bucket/zingg/your-model-id/` +{% endhint %} +{% endtab %} + +{% tab title="Azure Blob" %} +_**CHECK WITH SONAL - - NEED TEAMS HELP TO CHECK WHAT EXACTLY TO BE ADDED HERE**_ +{% endtab %} + +{% tab title="GCS" %} +On Google Cloud Storage, data lives in GCS buckets accessed via the `gs://` path format. Use `gs:///` to connect Zingg to your bucket. All formats — CSV, Parquet, JSON, Avro, Delta — are available in both Community and Enterprise. + +{% hint style="success" icon="right-long" %} +**Read more**: For the full Zingg installation, Dataproc cluster setup, and any additional dependency configuration required when running Zingg on Google Cloud, follow the [GCP Dataproc Platform Guide](../platform-guides/platform-guide-for-gcp-dataproc.md). +{% endhint %} + +### **Python API - Community** + +```python +from zingg.client import* +from zingg.pipes import* + +schema = ( + "id string, fname string, " + "lname string, stNo string, " + "add1 string, add2 string, " + "city string, state string, " + "areacode string, dob string, " + "ssn string" +) + +inputPipe = CsvPipe("testFebrl", "gs://your-bucket/test.csv", schema) +args.setData(inputPipe) + +outputPipe = CsvPipe("resultFebrl", "gs://your-bucket/Output") +args.setOutput(outputPipe) +``` + +### **Python API - Enterprise** + +```python +from zinggEC.enterprise.common.epipes import* +from zinggEC.enterprise.common.EArguments import* + +schema = ( + "id string, fname string, " + "lname string, stNo string, " + "add1 string, add2 string, " + "city string, state string, " + "areacode string, dob string, " + "ssn string" +) + +inputPipe = ECsvPipe("testFebrl", "gs://your-bucket/test.csv", schema) +args.setData(inputPipe) + +outputPipe = ECsvPipe("resultFebrl", "gs://your-bucket/Output") +outputPipe.setHeader("true") +args.setOutput(outputPipe) +``` + +### **JSON Config** + +```json +{ + "data" : [ { + "name" : "testFebrl", + "format" : "csv", + "props" : { + "location" : "gs://your-bucket/test.csv", + "delimiter" : ",", + "header" : "false" + }, + "schema" : "id string, fname string, lname string, stNo string, add1 " + "string, add2 string, city string, state string, areacode " + "string, dob string, ssn string" + } ], + "output" : [ { + "name" : "resultFebrl", + "format" : "csv", + "props" : { + "location" : "gs://your-bucket/Output", + "delimiter" : ",", + "header" : "true" + } + } ] +} +``` + +### **Setting a Spark checkpoint on GCS** + +For long-running Zingg jobs on Spark (especially on Dataproc), set a Spark checkpoint directory in GCS so Spark can recover state and manage memory during the entity resolution process: + +```python +checkpoint_path = "gs://your-bucket/zingg_checkpoint" +spark.sparkContext.setCheckpointDir(checkpoint_path) +``` + +The checkpoint directory is a Spark feature, not a Zingg-specific configuration. It provides Spark with persistent storage for intermediate computation metadata during multi-stage jobs. Recommended for any large-dataset Zingg run on Dataproc. + +### **Supported file formats on GCS** + +GCS supports all standard Spark formats. Replace `format` and the path suffix in the props. + +#### **Parquet** + +```json +{ + "data" : [ { + "name" : "parquetInput", + "format" : "parquet", + "props" : { + "path" : "gs://your-bucket/input.parquet" + } + } ] +} +``` + +#### **JSON** + +```json +{ + "data" : [ { + "name" : "jsonInput", + "format" : "json", + "props" : { + "path" : "gs://your-bucket/input.json" + } + } ] +} +``` + +#### **Avro** + +```json +{ + "data" : [ { + "name" : "avroInput", + "format" : "avro", + "props" : { + "path" : "gs://your-bucket/input.avro" + } + } ] +} +``` + +{% hint style="info" icon="right-long" %} +For Delta format on GCS, see the Azure Databricks tab — Delta is Enterprise only and works the same way with the path schema swapped to `gs://`. +{% endhint %} +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Read more**: + +* For cloud warehouses - [Connect Cloud Warehouses](connect-cloud-warehouses/) +* For flat files - [Connect File Formats](connect-file-formats.md) +{% endhint %} diff --git a/docs/connect-your-data/connect-cloud-warehouses/README.md b/docs/connect-your-data/connect-cloud-warehouses/README.md new file mode 100644 index 000000000..d9bb51ef6 --- /dev/null +++ b/docs/connect-your-data/connect-cloud-warehouses/README.md @@ -0,0 +1,19 @@ +--- +description: >- + Connect Zingg to cloud warehouses and analytics databases including + Databricks, Snowflake, BigQuery, Redshift, Fabric, and Exasol. +--- + +# Connect Cloud Warehouses + +{% hint style="success" icon="right-long" %} +Snowflake on this page means Snowflake as a DATA SOURCE, available in all editions. This is different from Snowflake as a COMPUTE ENGINE, which is Enterprise only and does not require Spark. For the Enterprise native Snowflake run, see [Install Zingg](../../running-zingg/install-zingg.md) +{% endhint %} + +Zingg connects to cloud warehouses using the Zingg pipe abstraction. + +{% hint style="success" icon="right-long" %} +New to Zingg pipes? Understand how pipes work before configuring them - [Pipes and data connections](../pipes-and-data-connections.md) +{% endhint %} + +Select your platform from the sub-pages below. Each page shows the Python API and JSON config for that platform, plus the supported file formats. Python and JSON produce identical results—use whichever matches your setup. diff --git a/docs/connect-your-data/connect-cloud-warehouses/connect-azure-databricks.md b/docs/connect-your-data/connect-cloud-warehouses/connect-azure-databricks.md new file mode 100644 index 000000000..e6fb57dea --- /dev/null +++ b/docs/connect-your-data/connect-cloud-warehouses/connect-azure-databricks.md @@ -0,0 +1,125 @@ +--- +description: >- + Connect Zingg to Azure Databricks using CsvPipe (Community) or ECsvPipe and + UCPipe (Enterprise). Supports CSV, Parquet, Avro, JSON, Delta, and Unity + Catalog +tags: + - tag: enterprise-only + primary: true +--- + +# Connect Azure Databricks + +{% hint style="info" icon="right-long" %} +All standard Spark formats (CSV, Parquet, JSON, Avro) are available in Community. Delta format and Unity Catalog are Enterprise only. +{% endhint %} + +### Prerequisites + +On Azure Databricks, data sits in cloud storage (Azure Data Lake Storage Gen2) or in Unity Catalog tables. Use the `abfss://` path format to connect Zingg to cloud storage paths, or use `UCPipe` (Enterprise only) for governed Unity Catalog tables. All standard Spark formats — CSV, Parquet, JSON, Avro — are available in both Community and Enterprise; Delta format and Unity Catalog are Enterprise-only. For the full Zingg installation, cluster setup, and any additional dependency configuration required on Databricks clusters, follow the [Azure Databricks Platform Guide](../../platform-guides/platform-guide-for-azure-databricks.md). + +### **Python API - Community** + +{% hint style="info" icon="right-long" %} +`dbfs:/` paths are deprecated in Databricks. Use Unity Catalog paths for tables or cloud storage paths (`s3a://`, `abfss://)` for files. For Unity Catalog table access, Enterprise uses UCPipe—see below. +{% endhint %} + +```python +from zingg.client import* +from zingg.pipes import* + +schema = "id string, fname string, \ +lname string, stNo string, add1 string, \ +add2 string, city string, \ +areacode string, state string, \ +dob string, ssn string" + +inputPipe = CsvPipe("databricksInput", "dbfs:/FileStore/input.csv", schema) +args.setData(inputPipe) + +outputPipe = CsvPipe("databricksOutput", "dbfs:/tmp/zinggOutput") +args.setOutput(outputPipe) +``` + +### Python API - Enterprise + +{% hint style="danger" icon="right-long" %} +**Enterprise** uses `ECsvPipe`. Supports all formats including Delta and Unity Catalog. +{% endhint %} + +```python +from zinggEC.enterprise.common.epipes import* +from zinggEC.enterprise.common.EArguments import* +``` + +For CSV or Parquet fields in cloud storage + +```python +schema = "rec_id string, fname string, \ +lname string, stNo string, add1 string, \ +add2 string, city string, \ +areacode string, state string, \ +dob string, ssn string" + +inputPipe = ECsvPipe("databricksInput", "abfss://path/to/input.csv", schema) +args.setData(inputPipe) + +outputPipe = ECsvPipe("databricksOutput", "abfss://path/to/output") +outputPipe.setHeader("true") +args.setOutput(outputPipe) +``` + +For Unity Catalog tables + +```python +inputPipe = UCPipe("databricksInput", "catalog.schema.tablename") +args.setData(inputPipe) +``` + +{% hint style="info" icon="right-long" %} +Enterprise supports Unity Catalog via UCPipe. Replace catalog, schema, and tablename with your Unity Catalog path. UCPipe is recommended for governed Delta tables in Databricks Enterprise deployments. +{% endhint %} + +### Supported file formats + +#### Parquet + +```json +{ + "data" : [ { + "name" : "parquetInput", + "format" : "parquet", + "props" : { + "path" : "/path/to/input.parquet" + } + } ] +} +``` + +#### Delta format—Enterprise only + +```json +{ + "data" : [ { + "name" : "deltaInput", + "format" : "delta", + "props" : { + "location" : "/path/to/delta/table" + } + } ] +} +``` + +#### Avro + +```json +{ + "data" : [ { + "name" : "avroInput", + "format" : "avro", + "props" : { + "path" : "dbfs:/FileStore/input.avro" + } + } ] +} +``` diff --git a/docs/connect-your-data/connect-cloud-warehouses/connect-bigquery.md b/docs/connect-your-data/connect-cloud-warehouses/connect-bigquery.md new file mode 100644 index 000000000..e0251eea2 --- /dev/null +++ b/docs/connect-your-data/connect-cloud-warehouses/connect-bigquery.md @@ -0,0 +1,82 @@ +--- +description: >- + Connect Zingg to Google BigQuery as a data source and output destination. + Requires two dependency JARs and Google Cloud credentials. +--- + +# Connect BigQuery + +### Prerequisites + +1. BigQuery requires two dependency JARs. Download and add to `zingg.conf.` + +```bash +spark.jars = ./ spark - bigquery - with - dependencies_2 .12 - 0.24.2.jar, \ +./ gcs - connector - hadoop2 - latest.jar +``` + +2. Set the Spark Hadoop property. + +```bash +spark.hadoop.fs.gs.impl = com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem +``` + +3. If running outside Google Cloud, set credentials. + +```bash +export GOOGLE_APPLICATION_CREDENTIALS = / path / to / service - account - key.json +``` + +For the full Zingg installation, cluster setup, and any additional dependency configuration required when connecting Spark to BigQuery, follow the [GCP Dataproc Platform Guide](../../platform-guides/platform-guide-for-gcp-dataproc.md). + +### Python API + +```python +from zingg.client import* +from zingg.pipes import* + +bqInput = BigQueryPipe("bqInput") +bqInput.addProperty("credentialsFile", "/path/to/key.json") +bqInput.addProperty("table", "project-id.dataset.tablename") +bqInput.addProperty("viewsEnabled", "true") +args.setData(bqInput) + +bqOutput = BigQueryPipe("bqOutput") +bqOutput.addProperty("credentialsFile", "/path/to/key.json") +bqOutput.addProperty("table", "project-id.dataset.outputtable") +bqOutput.addProperty("temporaryGcsBucket", "your-gcs-bucket") +args.setOutput(bqOutput) +``` + +### JSON Config + +```json +{ + "data" : [ { + "name" : "test", + "format" : "bigquery", + "props" : { + "credentialsFile" : "/path/to/key.json", + "table" : "project-id.dataset.table", + "viewsEnabled" : true + } + } ], + "output" : [ { + "name" : "output", + "format" : "bigquery", + "props" : { + "credentialsFile" : "/path/to/key.json", + "table" : "project-id.dataset.output", + "temporaryGcsBucket" : "your-gcs-bucket" + } + } ] +} +``` + +{% hint style="success" icon="right-long" %} +### **JAR download links:** + +* **spark-bigquery-with-dependencies:** repo1.maven.org/maven2/com/google/\ + cloud/spark/spark-bigquery-with-dependencies\_2.12/0.24.2/ +* **gcs-connector-hadoop2:** storage.googleapis.com/hadoop-lib/ gcs/gcs-connector-hadoop2-latest.jar +{% endhint %} diff --git a/docs/connect-your-data/connect-cloud-warehouses/connect-exasol.md b/docs/connect-your-data/connect-cloud-warehouses/connect-exasol.md new file mode 100644 index 000000000..6c00243a0 --- /dev/null +++ b/docs/connect-your-data/connect-cloud-warehouses/connect-exasol.md @@ -0,0 +1,78 @@ +--- +description: >- + Connect Zingg to Exasol using the Exasol Spark connector. Available in all + editions. +--- + +# Connect Exasol + +### Prerequisites + +1. Download the Exasol Spark connector assembled JAR (look for -assembly suffix). + +`github.com/exasol/spark-connector/releases` + +2. Add to `zingg.conf`. + +```markdown +spark.jars = spark - connector_2 .12 - 1.3.0 - spark - 3.3.2 - assembly.jar +``` + +### Python API + +```python +from zingg.client import * +from zingg.pipes import * + +exasolInput = Pipe("input", "com.exasol.spark") +exasolInput.addProperty("host", "10.11.0.2") +exasolInput.addProperty("port", "8563") +exasolInput.addProperty("username", "sys") +exasolInput.addProperty("password", "exasol") +exasolInput.addProperty("query", "SELECT * FROM DB_SCHEMA.CUSTOMERS") +args.setData(exasolInput) + +exasolOutput = Pipe("output", "com.exasol.spark") +exasolOutput.addProperty("host", "10.11.0.2") +exasolOutput.addProperty("port", "8563") +exasolOutput.addProperty("username", "sys") +exasolOutput.addProperty("password", "exasol") +exasolOutput.addProperty("create_table", "true") +exasolOutput.addProperty("table", "DB_SCHEMA.ENTITY_RESOLUTION") +args.setOutput(exasolOutput) +``` + +### JSON Config + +{% hint style="success" icon="right-long" %} +The host parameter must be the first internal node IPv4 address. Full Exasol connector options: `github.com/exasol/spark-connector/blob/main/doc/user_guide/user_guide.md` +{% endhint %} + +```json +{ + "data" : [ { + "name" : "input", + "format" : "com.exasol.spark", + "props" : { + "host" : "10.11.0.2", + "port" : "8563", + "username" : "sys", + "password" : "exasol", + "query" : "SELECT * FROM DB_SCHEMA.CUSTOMERS" + } + } ], + "output" : [ { + "name" : "output", + "format" : "com.exasol.spark", + "props" : { + "host" : "10.11.0.2", + "port" : "8563", + "username" : "sys", + "password" : "exasol", + "create_table" : "true", + "table" : "DB_SCHEMA.ENTITY_RESOLUTION" + }, + "mode" : "Append" + } ] +} +``` diff --git a/docs/connect-your-data/connect-cloud-warehouses/connect-microsoft-fabric.md b/docs/connect-your-data/connect-cloud-warehouses/connect-microsoft-fabric.md new file mode 100644 index 000000000..dc8ccfee9 --- /dev/null +++ b/docs/connect-your-data/connect-cloud-warehouses/connect-microsoft-fabric.md @@ -0,0 +1,109 @@ +--- +description: >- + Connect Zingg to Microsoft Fabric OneLake using abfss:// path format. + Available in all editions. +--- + +# Connect Microsoft Fabric + +{% hint style="success" icon="right-long" %} +On Microsoft Fabric, data lives in OneLake. Use the `abfss://` path format to connect Zingg to your Lakehouse. Available in all editions. +{% endhint %} + +### Prerequisites + +On Microsoft Fabric, data lives in OneLake. Use the `abfss://` path format to connect Zingg to your Lakehouse. The path follows the pattern `abfss://@onelake.dfs.fabric.microsoft.com//Files/` for files, and `.../Tables/` for Lakehouse tables. All formats — CSV, Parquet, Avro, JSON, Delta — are available in both Community and Enterprise. For the full Zingg installation, workspace setup, and any additional dependency configuration required on Fabric Spark pools, follow the [Microsoft Fabric Platform Guide](../../platform-guides/platform-guide-for-microsoft-fabric.md). + +### Python API + +```python +from zingg.client import * +from zingg.pipes import * + +schema = ( + "rec_id string, fname string, " + "lname string, stNo string, add1 string, " + "add2 string, city string, " + "areacode string, state string, " + "dob string, ssn string" +) + +workspace_id = "" +lakehouse_id = "" +base_path = ( + f"abfss://{workspace_id}@onelake.dfs.fabric.microsoft.com/" + f"{lakehouse_id}/Files" +) + +inputPipe = CsvPipe("inputpipe", f"{base_path}/yourdata.csv", schema) +args.setData(inputPipe) + +outputPipe = CsvPipe("resultOutput", f"{base_path}/Output") +args.setOutput(outputPipe) +``` + +### JSON Config + +```json +{ + "data" : [ { + "name" : "inputpipe", + "format" : "csv", + "props" : { + "location" : "abfss://@onelake.dfs.fabric.microsoft.com//Files/yourdata.csv" + }, + "schema" : "rec_id string, fname string, lname string, stNo string, add1 string, add2 string, city string, areacode string, state string, dob string, ssn string" + } ], + "output" : [ { + "name" : "resultOutput", + "format" : "csv", + "props" : { + "location" : "abfss://@onelake.dfs.fabric.microsoft.com//Files/Output" + } + } ] +} +``` + +### Supported file formats + +#### Parquet + +```json +{ + "data" : [ { + "name" : "parquetInput", + "format" : "parquet", + "props" : { + "path" : "abfss://@onelake.dfs.fabric.microsoft.com//Files/input.parquet" + } + } ] +} +``` + +#### **Delta (Lakehouse Tables)** + +```json +{ + "data" : [ { + "name" : "deltaInput", + "format" : "delta", + "props" : { + "location" : "abfss://@onelake.dfs.fabric.microsoft.com//Tables/yourtable" + } + } ] +} +``` + +#### **Avro** + +```json +{ + "data" : [ { + "name" : "avroInput", + "format" : "avro", + "props" : { + "path" : "abfss://@onelake.dfs.fabric.microsoft.com//Files/input.avro" + } + } ] +} +``` diff --git a/docs/connect-your-data/connect-cloud-warehouses/connect-redshift.md b/docs/connect-your-data/connect-cloud-warehouses/connect-redshift.md new file mode 100644 index 000000000..2c54bb933 --- /dev/null +++ b/docs/connect-your-data/connect-cloud-warehouses/connect-redshift.md @@ -0,0 +1,3 @@ +# Connect Redshift + +**Redshift connector documentation is being confirmed. This page will be updated with the JSON config, Python API, and supported formats once the connection details are verified.** diff --git a/docs/connect-your-data/connect-cloud-warehouses/connect-snowflake.md b/docs/connect-your-data/connect-cloud-warehouses/connect-snowflake.md new file mode 100644 index 000000000..edbfe9d57 --- /dev/null +++ b/docs/connect-your-data/connect-cloud-warehouses/connect-snowflake.md @@ -0,0 +1,72 @@ +--- +description: >- + Connect Zingg to Snowflake as a data source using the Snowflake Spark + connector. Available in all editions. +--- + +# Connect Snowflake + +{% hint style="success" icon="right-long" %} +This `config` connects Zingg to Snowflake as a DATA SOURCE using Spark. Available in all editions. +{% endhint %} + +{% hint style="warning" icon="right-long" %} +Snowflake as a COMPUTE ENGINE (native Snowflake run without Spark) is Enterprise only.\ +[Talk to us about Enterprise](https://www.zingg.ai/company/contact/contact). +{% endhint %} + +### Prerequisites + +Snowflake as a data source requires two dependency JARs on the Spark classpath. Download from `Maven` and add to `zingg.conf.` + +```bash +spark.jars = snowflake - jdbc - 3.13.19.jar, \ +spark - snowflake_2 .12 - 2.10.0 - spark_3 .1.jar +``` + +For the full Zingg installation, cluster setup, and any additional dependency configuration required when connecting Spark to Snowflake, follow the [Snowflake Platform Guide](../../platform-guides/platform-guide-for-snowflake.md). + +### Python API + +```python +from zingg.client import* +from zingg.pipes import* + +snowflakePipe = SnowflakePipe("snowflakeInput") +snowflakePipe.addProperty("sfUrl", "rfa59271.snowflakecomputing.com") +snowflakePipe.addProperty("sfUser", "sonalgoyal") +snowflakePipe.addProperty("sfPassword", "ZZ") +snowflakePipe.addProperty("sfDatabase", "TEST") +snowflakePipe.addProperty("sfSchema", "PUBLIC") +snowflakePipe.addProperty("sfWarehouse", "COMPUTE_WH") +snowflakePipe.addProperty("dbtable", "FEBRL") +snowflakePipe.addProperty("application", "zingg_zingg") +args.setData(snowflakePipe) +``` + +### JSON Config + +```json +{ + "data" : [ { + "name" : "test", + "format" : "net.snowflake.spark.snowflake", + "props" : { + "sfUrl" : "rfa59271.snowflakecomputing.com", + "sfUser" : "sonalgoyal", + "sfPassword" : "ZZ", + "sfDatabase" : "TEST", + "sfSchema" : "PUBLIC", + "sfWarehouse" : "COMPUTE_WH", + "dbtable" : "FEBRL", + "application" : "zingg_zingg" + } + } ] +} +``` + +{% hint style="success" icon="right-long" %} +### Supported file formats + +Snowflake as a data source connects to tables and views via the Snowflake Spark connector. File formats (CSV, Parquet) are handled by Snowflake internally before Zingg reads the data. No additional format config needed on this connector. +{% endhint %} diff --git a/docs/connect-your-data/connect-file-formats.md b/docs/connect-your-data/connect-file-formats.md new file mode 100644 index 000000000..3786a2f07 --- /dev/null +++ b/docs/connect-your-data/connect-file-formats.md @@ -0,0 +1,244 @@ +--- +description: >- + Configure Zingg pipes for CSV, Parquet, Avro, JSON, TSV, and XML flat file + formats. +--- + +# Connect File Formats + +{% hint style="success" icon="right-long" %} +New to Zingg pipes? Understand how pipes work before configuring them - [Pipes and data connections](pipes-and-data-connections.md). +{% endhint %} + +This page covers standalone flat file connections - reading and writing files directly from a local path or mounted file system. + +For files stored in cloud platforms, use the format sections within each platform tab. + +* Files on Databricks or Fabric - Connect cloud warehouses +* Files on S3 - Connect cloud storage + +{% hint style="success" icon="right-long" %} +All file formats shown here are available in Community and Enterprise. + +The only edition difference is the pipe class in the Python API: + +* `CsvPipe` for Community +* `ECsvPipe` for Enterprise +{% endhint %} + +{% tabs %} +{% tab title="CSV" %} +### Python API - Community + +```python +from zingg.client import* +from zingg.pipes import* + +schema = "field1 string, \ +field2 string, field3 string" + +inputPipe = CsvPipe("csvInput", "/path/to/input.csv", schema) +args.setData(inputPipe) + +outputPipe = CsvPipe("csvOutput", "/path/to/output") +args.setOutput(outputPipe) +``` + +### Python API - Enterprise + +{% hint style="info" icon="right-long" %} +Enterprise uses ECsvPipe. The pipe setup is identical - only the class `name` and `import` changes. +{% endhint %} + +```python +from zinggEC.enterprise.common.epipes import* +from zinggEC.enterprise.common.EArguments import* + +schema = "field1 string, \ +field2 string, field3 string" + +inputPipe = ECsvPipe("csvInput", "/path/to/input.csv", schema) +args.setData(inputPipe) + +outputPipe = ECsvPipe("csvOutput", "/path/to/output") +outputPipe.setHeader("true") +args.setOutput(outputPipe) +``` + +### JSON Config + +```json +{ + "data" : [ { + "name" : "csvInput", + "format" : "csv", + "props" : { + "location" : "/path/to/input.csv", + "delimiter" : ",", + "header" : "true" + }, + "schema" : "field1 string, + field2 string, + field3 string " + } ], + "output" : [ { + "name" : "csvOutput", + "format" : "csv", + "props" : {"location" : "/path/to/output", "header" : "true"} + } ] +} +``` + +{% hint style="success" icon="right-long" %} +If your CSV has no header row, set `header`: `false` and define the schema field explicitly. + +Schema uses Spark SQL types: string, integer, double, date, timestamp. +{% endhint %} +{% endtab %} + +{% tab title="Parquet" %} +### Python API + +```python +from zingg.client import* +from zingg.pipes import* + +parquetPipe = Pipe("parquetFiles", "parquet") +parquetPipe.addProperty("path", "/home/zingg") +args.setData(parquetPipe) +``` + +### **JSON Config** + +{% hint style="danger" icon="right-long" %} +Parquet uses `path` as the property key, not `location` as CSV uses. +{% endhint %} + +```json +{ + "data" : [ { + "name" : "parquetFiles", + "format" : "parquet", + "props" : { + "path" : "/home/zingg" + } + } ] +} +``` +{% endtab %} + +{% tab title="Avro" %} +### Python API + +```python +from zingg.client import* +from zingg.pipes import* + +avroPipe = Pipe("avroInput", "avro") +avroPipe.addProperty("path", "/path/to/input.avro") +args.setData(avroPipe) +``` + +### JSON Config + +```json +{ + "data" : [ { + "name" : "avroInput", + "format" : "avro", + "props" : { + "path" : "/path/to/input.avro" + } + } ] +} +``` +{% endtab %} + +{% tab title="JSON" %} +### Python API + +```python +from zingg.client import* +from zingg.pipes import* + +jsonPipe = Pipe("jsonInput", "json") +jsonPipe.addProperty("path", "/path/to/input.json") +args.setData(jsonPipe) +``` + +### JSON Config + +```json +{ + "data" : [ { + "name" : "jsonInput", + "format" : "json", + "props" : { + "path" : "/path/to/input.json" + } + } ] +} +``` + +{% hint style="info" icon="right-long" %} +* For multiline JSON files, add `multiLine`: `true` to props. +* For JSON Lines (one object per line), no additional props needed. +{% endhint %} +{% endtab %} + +{% tab title="XML" %} +_**XML connector documentation is being confirmed. This section will be updated with the JSON config and Python API once verified**_ +{% endtab %} +{% endtabs %} + +
+ +What about TSV and XLSX? + +**TSV** + +TSV uses the same CSV connector with a tab delimiter. + +```json +{ + "data": [ + { + "name": "tsvInput", + "format": "csv", + "props": { + "location": "/path/to/input.tsv", + "delimiter": "\t", + "header": "true" + } + } + ] +} +``` + +**Python API - Community** + +```python +from zingg.client import * +from zingg.pipes import * + +tsvPipe = CsvPipe( + "tsvInput", + "/path/to/input.tsv", + schema +) +tsvPipe.addProperty("sep", "\t") +args.setData(tsvPipe) +``` + +**XLSX** + +_**CHECK WITH SONAL - XLSX is listed as supported on zingg.ai but the connector format string and Python class are not confirmed on any live docs page. Please confirm the XLSX config and whether it is all editions or ENT only.**_ + +{% hint style="success" icon="right-long" %} +For files on cloud platforms: + +* Databricks and Fabric - [Connect Cloud Warehouses](connect-cloud-warehouses/) +* S3 - [Connect Cloud Storage](connect-cloud-storage.md) +{% endhint %} + +
diff --git a/docs/connect-your-data/connect-graph-databases-neo4j.md b/docs/connect-your-data/connect-graph-databases-neo4j.md new file mode 100644 index 000000000..2331b45d1 --- /dev/null +++ b/docs/connect-your-data/connect-graph-databases-neo4j.md @@ -0,0 +1,42 @@ +--- +description: >- + Configure Zingg to connect to Neo4j for writing resolved entity output to a + graph database. +--- + +# Connect Graph Databases (Neo4j) + +{% hint style="success" icon="right-long" %} +New to Zingg pipes? Understand how pipes work before configuring them - [Pipes and data connections](pipes-and-data-connections.md). +{% endhint %} + +The Neo4j connection configuration is available on this page. Zingg resolves entities into clusters and writes them to Neo4j, where the graph database handles downstream relationship analysis. Available in all editions. + +### Python API + +```python +from zingg.client import* +from zingg.pipes import* + +neo4jPipe = +Pipe("neo", "org.neo4j.spark.DataSource") +neo4jPipe.addProperty("url", "bolt://localhost:7687") +neo4jPipe.addProperty("labels", "Person") +args.setData(neo4jPipe) +``` + +### JSON Config + +```json +{ + "data" : [ { + "name" : "neo", + "format" : "org.neo4j.spark.DataSource", + "props" : {"url" : "bolt://localhost:7687", "labels" : "Person"} + } ] +} +``` + +The `url` property is the Neo4j bolt connection URI. The labels property sets the Neo4j node label for the resolved entities that Zingg writes to the graph. + +The Neo4j Spark connector JAR is required. Add it to your Spark classpath via `zingg.conf` before running. diff --git a/docs/connect-your-data/connect-nosql-databases.md b/docs/connect-your-data/connect-nosql-databases.md new file mode 100644 index 000000000..f898bc248 --- /dev/null +++ b/docs/connect-your-data/connect-nosql-databases.md @@ -0,0 +1,102 @@ +--- +description: >- + Connect Zingg to Cassandra and MongoDB as data sources and output + destinations. +--- + +# Connect NoSQL Databases + +{% hint style="success" icon="right-long" %} +New to Zingg pipes? Understand how pipes work before configuring them - [Pipes and data connections](pipes-and-data-connections.md). +{% endhint %} + +{% hint style="success" icon="right-long" %} +This page covers NoSQL database connections. + +* For relational databases see [Connect Relational Databases](connect-relational-databases.md). +* For cloud warehouses see [Connect Cloud Warehouses](connect-cloud-warehouses/). + +All NoSQL connectors are available in Community and Enterprise. +{% endhint %} + +{% tabs %} +{% tab title="Cassandra" %} +{% hint style="success" icon="right-long" %} +The config below shows Cassandra as a write destination (output). + +To read FROM Cassandra, use the same props under "`data`" with `args.setData()`. +{% endhint %} + +### Python API + +```python +from zingg.client import* +from zingg.pipes import* + +cassandraPipe = +Pipe("sampleTest", "CASSANDRA") +cassandraPipe.addProperty("table", "dataschematest") +cassandraPipe.addProperty("keyspace", "zingg") +cassandraPipe.addProperty("cluster", "zingg") +cassandraPipe.addProperty("spark.cassandra.connection.host", "192.168.0.6") +args.setOutput(cassandraPipe) +``` + +### **JSON Config** + +```json +{ + "output" : [ { + "name" : "sampleTest", + "format" : "CASSANDRA", + "props" : { + "table" : "dataschematest", + "keyspace" : "zingg", + "cluster" : "zingg", + "spark.cassandra.connection.host" : "192.168.0.6" + }, + "sparkProps" : {"spark.cassandra.connection.host" : "127.0.0.1"}, + "mode" : "Append" + } ] +} +``` +{% endtab %} + +{% tab title="MongoDB" %} +### Python API + +```python +from zingg.client import* +from zingg.pipes import* + +mongoPipe = +Pipe("mongodb", "mongo") +mongoPipe.addProperty("uri", "mongodb://127.0.0.1/people.contacts") +args.setData(mongoPipe) +``` + +### JSON Config + +```json +{ + "data" : [ { + "name" : "mongodb", + "format" : "mongo", + "props" : {"uri" : "mongodb://127.0.0.1/people.contacts"} + } ] +} +``` + +{% hint style="danger" icon="right-long" %} +* URI format: `mongodb://host:port/database.collection` +* Authenticated: `mongodb://user:password@host:port/database.collection` +{% endhint %} + +{% hint style="success" icon="right-long" %} +**Read more**: + +* For relational databases - [Connect Relational Databases](connect-relational-databases.md) +* For cloud warehouses - [Connect Cloud Warehouses](connect-cloud-warehouses/) +{% endhint %} +{% endtab %} +{% endtabs %} diff --git a/docs/connect-your-data/connect-relational-databases.md b/docs/connect-your-data/connect-relational-databases.md new file mode 100644 index 000000000..50061da2a --- /dev/null +++ b/docs/connect-your-data/connect-relational-databases.md @@ -0,0 +1,131 @@ +--- +description: >- + Connect Zingg to MySQL, PostgreSQL, SQL Server, DB2, MariaDB, and Oracle via + JDBC. +--- + +# Connect Relational Databases + +{% hint style="success" icon="right-long" %} +New to Zingg pipes? Understand how pipes work before configuring them - [Pipes and data connections](pipes-and-data-connections.md). +{% endhint %} + +Zingg connects to relational databases using the JDBC connector. This covers any JDBC-compatible database including MySQL, PostgreSQL, SQL Server, DB2, MariaDB, and Oracle. Available in all editions. + +Download the JDBC driver for your database and add to `zingg.conf`. + +``` +spark.jars= +``` + +{% tabs %} +{% tab title="PostgreSQL" %} +### Python API + +```python +from zingg.client import* +from zingg.pipes import* + +jdbcPipe = +Pipe("pgInput", "jdbc") +jdbcPipe.addProperty("url", "jdbc:postgresql://localhost:5432/dbname") +jdbcPipe.addProperty("dbtable", "your_table") +jdbcPipe.addProperty("driver", "org.postgresql.Driver") +jdbcPipe.addProperty("user", "postgres") +jdbcPipe.addProperty("password", "your_password") +args.setData(jdbcPipe) +``` + +### JSON Config + +```json +{ + "data" : [ { + "name" : "test", + "format" : "jdbc", + "props" : { + "url" : "jdbc:postgresql://localhost:5432/", + "dbtable" : "", + "driver" : "org.postgresql.Driver", + "user" : "", + "password" : "" + } + } ] +} +``` +{% endtab %} + +{% tab title="MySQL" %} +### Python API + +```python +from zingg.client import* +from zingg.pipes import* + +jdbcPipe = Pipe("mysqlInput", "jdbc") +jdbcPipe.addProperty("url", "jdbc:mysql://localhost:3306/") +jdbcPipe.addProperty("dbtable", "testData") +jdbcPipe.addProperty("driver", "com.mysql.cj.jdbc.Driver") +jdbcPipe.addProperty("user", "root") +jdbcPipe.addProperty("password", "password") +args.setData(jdbcPipe) +``` + +### JSON Config + +```json +{ + "data" : [ { + "name" : "test", + "format" : "jdbc", + "props" : { + "url" : "jdbc:mysql://localhost:3306/", + "dbtable" : "testData", + "driver" : "com.mysql.cj.jdbc.Driver", + "user" : "root", + "password" : "password" + } + } ] +} +``` +{% endtab %} +{% endtabs %} + +
+ +SQL Server, DB2, MariaDB, Oracle and other JDBC databases + +For any JDBC-compatible database, use the same pattern as PostgreSQL. Change two things only: + +1. The url format for that database +2. The driver class name + +**Common driver class names and URL formats:** + +* **SQL Server:** + * **url**: `jdbc:sqlserver://host:1433;databaseName=dbname` + * **driver**: `com.microsoft.sqlserver.jdbc.SQLServerDriver` +* **MySQL:** + * **url**: `jdbc:mysql://host:3306/dbname` + * **driver**: `com.mysql.cj.jdbc.Driver` +* **DB2:** + * **url**: `jdbc:db2://host:50000/dbname` + * **driver**: `com.ibm.db2.jcc.DB2Driver` +* **MariaDB:** + * **url**: `jdbc:mariadb://host:3306/dbname` + * **driver**: `org.mariadb.jdbc.Driver` +* **Oracle:** + * **url**: `jdbc:oracle:thin:@host:1521:dbname` + * **driver**: `oracle.jdbc.OracleDriver` + +Download the JDBC driver JAR from the database vendor and add it to `spark.jars`\ +in `zingg.conf` before running. + +
+ +{% hint style="success" icon="right-long" %} +**Read more:** + +* For NoSQL databases - [Connect NoSQL databases](connect-nosql-databases.md) +* For cloud warehouses - [Connect Cloud Warehouses](connect-cloud-warehouses/) +{% endhint %} diff --git a/docs/connect-your-data/pipes-and-data-connections.md b/docs/connect-your-data/pipes-and-data-connections.md new file mode 100644 index 000000000..945cc2ca0 --- /dev/null +++ b/docs/connect-your-data/pipes-and-data-connections.md @@ -0,0 +1,159 @@ +--- +description: What a Zingg pipe is and how you configure one to connect your data. +--- + +# Pipes and Data Connections + +A Zingg pipe is a configuration object that tells Zingg where to find your source data and where to write resolved output. It is how Zingg connects to your datastore, whether that is a\ +CSV file, a Delta table, a Snowflake table, a Parquet path on S3, or any other Spark-supported connector. + +You configure a pipe by giving it a name, a format, and the connection properties for your specific datastore. The pipe is then passed to your Zingg arguments object `args.setData()`.\ +for input, `args.setOutput()` for output. + +### What every pipe configuration contains + +Every pipe, whether input or output, has three attributes. + +
AttributeWhat it does
nameA unique label for this pipe. Used internally by Zingg to identify the data source or destination in logs and output.
formatThe Spark connector format string for your datastore: csv, parquet, delta, net.snowflake.spark.snowflake, jdbc, avro, and others.
props / optionsThe connection properties are passed to spark.read and
spark.write path, delimiter, header, credentials, and any connector-
specific settings.
+ +### Configuring a pipe + +You can configure pipes in two ways. Both work in Community and Enterprise. Use whichever fits your workflow. + +{% tabs %} +{% tab title="Python API" %} +Create pipe objects and attach them to your arguments object. + +### **Community** + +```python +from zingg.client import * +from zingg.pipes import * +``` + +#### **Input Pipe** + +```python +inputPipe = CsvPipe( + "testFebrl", + "examples/febrl/test.csv", + schema) +args.setData(inputPipe) +``` + +#### **Output Pipe** + +```python +outputPipe = CsvPipe( + "resultFebrl", + "/tmp/febrlOutput") +args.setOutput(outputPipe) +``` + +### **Enterprise** + +```python +from zinggEC.enterprise.common.epipes import * +from zinggEC.enterprise.common.EArguments import * +``` + +#### **Input Pipe** + +```python +inputPipe = ECsvPipe("testFebrl", "examples/febrl/test.csv", schema) +args.setData(inputPipe) +``` + +#### **Output Pipe** + +```python +outputPipe = ECsvPipe("resultFebrl", "/tmp/febrlOutput") +outputPipe.addProperty("header", "true") +args.setOutput(outputPipe) +``` +{% endtab %} + +{% tab title="JSON config" %} +Define pipes in the data and output sections of your JSON config file. + +### **Community** + +```json +{ + "data" : [ { + "name" : "testFebrl", + "format" : "csv", + "props" : { + "location" : "examples/febrl/test.csv", + "delimiter" : ",", + "header" : "false" + } + } ], + "output" : [ { + "name" : "resultFebrl", + "format" : "csv", + "props" : { + "location" : "/tmp/febrlOutput", + "delimiter" : ",", + "header" : "true" + } + } ] +} +``` + +### **Enterprise** + +```json +{ + "data" : [ { + "name" : "testFebrl", + "format" : "csv", + "props" : { + "location" : "examples/febrl/test.csv", + "delimiter" : ",", + "header" : "false" + }, + "schema" : "id string, fname string, lname string, stNo string, add1 string, add2 string, city string, state string, areacode string, dob string, ssn string" + } ], + "output" : [ { + "name" : "OUTPUT_FEBRL", + "format" : "csv", + "props" : { + "location" : "/tmp/zinggOutputNew/", + "delimiter" : ",", + "header" : true + } + } ], + "outputStats" : { + "name" : "stats", + "format" : "csv", + "props" : { + "location" : "/tmp/zinggStats_$ZINGG_DYNAMIC_STAT_NAME", + "delimiter" : ",", + "header" : true + } + } +} +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +`CsvPipe` and the standard `Pipe` class are Community APIs. `ECsvPipe`, `UCPipe`, and the other `E*Pipe` classes are Enterprise APIs that add features like Unity Catalog table access, Lakehouse table support, and stats output. The connection properties and basic pipe operations are identical, only the class name and the import path change. + +See [Install Zingg → Snowflake](../running-zingg/install-zingg.md) for the full setup. +{% endhint %} + +### What you can connect Zingg to + +Zingg connects to any datastore that has a Spark connector, plus Snowflake natively in Enterprise. The pipe format string is all that changes. + +
DatastoreFormat stringEdition
CSV / TSVcsvAll
ParquetparquetAll
AvroavroAll
JSONjsonAll
Delta tables (Databricks)deltaAll
Unity Catalog (Databricks)deltaAll
OneLake / Fabricabfss:// path + csv or parquetAll
Snowflake (as data source via Spark)net.snowflake.spark.snowflakeAll
Snowflake (native — no Spark cluster)Configured via Snowflake properties file—no pipe classEnterprise
AWS S3csv / parquet via s3a://All
Google Cloud Storagecsv / parquet via gs://All
BigQuerycom.google.cloud.spark.bigqueryAll
PostgreSQL / MySQL / JDBCjdbcAll
CassandraCASSANDRAAll
MongoDBmongoAll
Neo4jorg.neo4j.spark.DataSourceAll
Exasolcom.exasol.sparkAll
Redshiftjdbc (redshift driver)All
+ +{% hint style="success" icon="right-long" %} +**Read more**: + +* For the full connection config including required JARs, props, and code examples for each datastore - [Connect Data](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/connect-your-data) +* To configure your pipes step by step as part of your Zingg setup - [Configure Zingg](../running-zingg/configure-zingg.md) +* To understand what Zingg adds to your output alongside your input fields (`Z_CLUSTER`, `Z_MINSCORE`, `Zingg ID` and others) - [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) +{% endhint %} diff --git a/docs/contributing/contributing-to-zingg.md b/docs/contributing/contributing-to-zingg.md new file mode 100644 index 000000000..b57cfa0d9 --- /dev/null +++ b/docs/contributing/contributing-to-zingg.md @@ -0,0 +1,22 @@ +--- +description: >- + Report a bug, request a feature, or contribute code to Zingg. All code + contributions require signing the Contributor License Agreement. +--- + +# Contributing to Zingg + +{% hint style="success" icon="right-long" %} +* Want to report a bug or request a feature? Let us know on [Slack](https://join.slack.com/t/zinggai/shared_invite/zt-w7zlcnol-vEuqU9m~Q56kLLUVxRgpOA), or open an [issue](https://github.com/zinggAI/zingg/issues/new/choose). +* Want to commit code? Please take a look at the [open issues](https://github.com/zinggAI/zingg/issues) or choose the ones marked [good first issue](https://github.com/zinggAI/zingg/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+). Let us talk on [Slack](https://join.slack.com/t/zinggai/shared_invite/zt-w7zlcnol-vEuqU9m~Q56kLLUVxRgpOA) if you need help deciding what to fix or how. +{% endhint %} + +As the sponsor of Zingg, Zingg Labs would like to ensure the long-term viability of Zingg and its community. For all code contributions to Zingg, we ask that contributors complete and sign a Contributor License Agreement. The Contributor License Agreement helps ensure everyone can enjoy Zingg with confidence that Zingg is here to stay. + +Specifically, our Contributor License Agreement (CLA) grants the contributor and Zingg Labs joint copyright interest in contributed code. Further, it provides assurance from the contributor that contributions are original work that does not violate any third-party license agreement. The agreement between contributors and the Zingg project is explicit. This ensures that Zingg users can be confident in the legal status of the source code and their right to use it. + +* [Individual Contributor License Agreement v1.0](https://forms.gle/wU46nGvTMM2wSzy5A) + +The above agreement is based on the Contributor License Agreements published by the [Apache Software Foundation](http://www.apache.org/), with modifications: + +* [Individual Contributor License Agreement ("Agreement") V2.0](http://apache.org/licenses/icla.pdf) diff --git a/docs/contributing/setting-up-zingg-development-environment/README.md b/docs/contributing/setting-up-zingg-development-environment/README.md new file mode 100644 index 000000000..4fd7ebdbf --- /dev/null +++ b/docs/contributing/setting-up-zingg-development-environment/README.md @@ -0,0 +1,14 @@ +--- +description: >- + Install and configure everything you need to build and contribute to Zingg + locally. Step-by-step setup for macOS, Ubuntu, and WSL2 +--- + +# Setting Up Zingg Development Environment + +Welcome to the Zingg development setup guide. To provide you with the most accurate instructions, please select your operating system: + +* [**macOS Setup Guide**](https://docs.zingg.ai/latest/contributing/settingupzingg/macossetup) +* [**Ubuntu / WSL2 Setup Guide**](https://docs.zingg.ai/latest/contributing/settingupzingg/ubuntusetup) + +If you're using a different Linux distribution, the Ubuntu instructions will likely be the most relevant, but you may need to adjust package management commands (e.g., using `yum` or `dnf` instead of `apt`). diff --git a/docs/contributing/setting-up-zingg-development-environment/macos-setup-guide.md b/docs/contributing/setting-up-zingg-development-environment/macos-setup-guide.md new file mode 100644 index 000000000..dd5bade4c --- /dev/null +++ b/docs/contributing/setting-up-zingg-development-environment/macos-setup-guide.md @@ -0,0 +1,164 @@ +--- +description: >- + Step-by-step instructions for setting up the Zingg development environment on + macOS. Install Homebrew, Java 11, Spark 3.5, Maven, then build Zingg from + source and run the full match workflow. +--- + +# macOS Setup Guide + +This guide provides specific instructions for setting up the Zingg development environment on macOS. + +### **Step 0: Initial OS Setup** + +Homebrew is the preferred package manager for macOS and makes installing system dependencies much easier. While you can install dependencies manually, this guide uses Homebrew for simplicity. + +#### **Install Homebrew** + +```bash +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +``` + +{% hint style="success" icon="right-long" %} +**For Apple Silicon:** If `brew` is not found, run: `echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zshrc && source ~/.zshrc` +{% endhint %} + +### **Step 1: Clone The Zingg Repository** + +```bash +brew install git +``` + +{% hint style="success" icon="right-long" %} +It is suggested to fork the repository to your account and then clone the repository. +{% endhint %} + +```bash +git clone https://github.com/zinggAI/zingg.git +``` + +### **Step 2: Install JDK 11 (Java Development Kit)** + +1. Install OpenJDK 11: `brew install openjdk@11` +2. Link Java 11: + +* **Apple Silicon:** `sudo ln -sfn /opt/homebrew/opt/openjdk@11/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-11.jdk` +* **Intel:** `sudo ln -sfn /usr/local/opt/openjdk@11/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-11.jdk` + +### **Step 3: Install Apache Spark** + +Download Apache Spark from the [Apache Spark Official Website](https://spark.apache.org/downloads.html). + +For example for 3.5.0: + +```bash +curl -O https://archive.apache.org/dist/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz +tar -xvf spark-3.5.0-bin-hadoop3.tgz +sudo mv spark-3.5.0-bin-hadoop3 /opt/spark +rm spark-3.5.0-bin-hadoop3.tgz +``` + +Make sure that Spark version you have installed is compatible with Java you have installed, and Zingg is supporting those versions. + +{% hint style="success" icon="right-long" %} +Zingg supports Spark 3.5 and the corresponding Java version. +{% endhint %} + +### **Step 4: Install Apache Maven** + +```bash +brew install maven +``` + +### **Step 5: Update Environment Variables (\~/.zshrc)** + +```bash +export JAVA_HOME=/Library/Java/JavaVirtualMachines/openjdk-11.jdk/Contents/Home +export SPARK_HOME=/opt/spark +export SPARK_MASTER=local[*] +export ZINGG_HOME=/assembly/target +export PATH=$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin:$JAVA_HOME/bin +``` + +`` will be a directory where you clone the repository of the Zingg. Similarly, if you have installed spark on a different directory you can set **SPARK\_HOME** accordingly. + +**Save/exit and do source `.zshrc` so that they reflect** + +```bash +source ~/.zshrc +``` + +**Verify** + +```bash +echo $PATH +mvn --version +``` + +### **Step 6: Compile The Zingg Repository** + +1. **Make sure you are executing the following commands in the same terminal window where you saved the .zshrc. Run the following to compile the Zingg Repository:** + +```bash +git branch +``` + +2. **Run the following to Compile the Zingg Repository:** + +```bash +mvn initialize +mvn clean compile package -Dspark=sparkVer +``` + +3. **Run the following to Compile while skipping tests:** + +```bash +mvn initialize +mvn clean compile package -Dspark=sparkVer -Dmaven.test.skip=true +``` + +{% hint style="success" icon="right-long" %} +Replace the `sparkVer` with the version of Spark you installed. For example, **-Dspark=3.5**. If you still face an error, include **-Dmaven.test.skip=true** with the above command. +{% endhint %} + +### **Step 7: If you have any issue with 'SPARK\_LOCAL\_IP'** + +1. **Find your IP:** `ipconfig getifaddr en0` +2. **Find your Hostname:** `hostname` +3. **Update hosts:** `sudo nano /etc/hosts` and add `[your-ip] [your-hostname]` at the bottom. + +### **Step 8: Run Zingg To Find Training Data** + +Run this script in the terminal opened in Zingg clones directory: + +```bash +./scripts/zingg.sh --phase findTrainingData --conf examples/febrl/config.json +``` + +_If everything is right, it should show Zingg banner._ + +### **Step 9: Run Zingg To Label Data** + +Run this script in the terminal opened in Zingg clones directory: + +```bash +./scripts/zingg.sh --phase label --conf examples/febrl/config.json --properties-file config/zingg.conf +``` + +### **Step 10: Run Zingg To Train Model Based On Labeling** + +Run this script in the terminal opened in Zingg clones directory: + +```bash +./scripts/zingg.sh --phase train --conf examples/febrl/config.json --properties-file config/zingg.conf +``` + +### **Step 11: Run Zingg To Prepare Final Output Data** + +Run this script in the terminal opened in Zingg clones directory: + +```bash +./scripts/zingg.sh --phase match --conf examples/febrl/config.json --properties-file config/zingg.conf +``` + +Change directory `cd /tmp/zinggOutput` (or the path provided in your config file) to see the output files. diff --git a/docs/contributing/setting-up-zingg-development-environment/ubuntu-wsl2-setup-guide.md b/docs/contributing/setting-up-zingg-development-environment/ubuntu-wsl2-setup-guide.md new file mode 100644 index 000000000..8e6b97922 --- /dev/null +++ b/docs/contributing/setting-up-zingg-development-environment/ubuntu-wsl2-setup-guide.md @@ -0,0 +1,206 @@ +--- +description: >- + Step-by-step instructions for setting up the Zingg development environment on + Ubuntu and WSL2. Install Java 11, Spark 3.5, Maven, then build Zingg from + source and run the full match workflow. +--- + +# Ubuntu/WSL2 Setup Guide + +The following steps will help you set up the Zingg Development Environment on **Ubuntu/WSL2**. + +### **Step 0: Initial OS Setup (Ubuntu/WSL2)** + +Make sure to update your Ubuntu installation: + +`sudo apt update` + +#### **Install Ubuntu on WSL2 on Windows** + +1. Install **wsl**: Type the following command in **Windows PowerShell**. + +```bash +wsl --install +``` + +2. Download Ubuntu from **Microsoft Store**, **Ubuntu 20.04 LTS** +3. Configure Ubuntu with a **username** and **password** +4. Open **Ubuntu 20.04 LTS** and start working + +```bash +sudo apt update +``` + +{% hint style="success" icon="right-long" %} +Follow this [tutorial](https://ubuntu.com/tutorials/install-ubuntu-on-wsl2-on-windows-10#1-overview) for more information. +{% endhint %} + +### **Step 1: Clone The Zingg Repository (Ubuntu)** + +1. Install and SetUp Git: **sudo apt install git** +2. Verify : **git --version** +3. Set up Git by following the [tutorial](https://www.digitalocean.com/community/tutorials/how-to-install-git-on-ubuntu-20-04). +4. Clone the Zingg Repository: `git clone https://github.com/zinggAI/zingg.git` + +{% hint style="success" icon="right-long" %} +It is suggested to fork the repository to your account and then clone the repository. +{% endhint %} + +### **Step 2: Install JDK 11 (Ubuntu)** + +Follow this [tutorial](https://linuxize.com/post/install-java-on-ubuntu-20-04/) to install Java 11 JDK 11 in Ubuntu. + +For example: + +```bash +sudo apt install openjdk-11-jdk openjdk-11-jre +javac -version +java -version +``` + +### **Step 3: Install Apache Spark** + +#### **Common Steps** + +Download Apache Spark - from the [Apache Spark Official Website](https://spark.apache.org/downloads.html). + +* For example, 3.5.0: + +```bash +curl -O https://archive.apache.org/dist/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz +tar -xvf spark-3.5.0-bin-hadoop3.tgz +sudo mv spark-3.5.0-bin-hadoop3 /opt/spark +rm spark-3.5.0-bin-hadoop3.tgz +``` + +#### **Original Ubuntu Instructions (Manual Wget)** + +Install the downloaded Apache Spark - on your Ubuntu by following [this tutorial](https://computingforgeeks.com/how-to-install-apache-spark-on-ubuntu-debian/). + +* For example, 3.5.0: + +```bash +wget https://www.apache.org/dyn/closer.lua/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz +tar -xvf spark-3.5.0-bin-hadoop3.tgz +rm -rf spark-3.5.0-bin-hadoop3.tgz +sudo mv spark-3.5.0-bin-hadoop3 /opt/spark +``` + +Make sure that Spark version you have installed is compatible with Java you have installed, and Zingg is supporting those versions. + +{% hint style="success" icon="right-long" %} +Zingg supports Spark 3.5 and the corresponding Java version. +{% endhint %} + +### **Step 4: Install Apache Maven (Ubuntu)** + +Install the latest **maven** package. + +* For example, 3.8.8: + +```bash +wget https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz +tar -xvf apache-maven-3.8.8-bin.tar.gz +rm -rf apache-maven-3.8.8-bin.tar.gz +cd apache-maven-3.8.8/ +cd bin +./mvn --version +``` + +{% hint style="success" icon="right-long" %} +Make sure that mvn -version should display correct java version as well(JAVA 11) + +Apache Maven 3.8.7 + +* Maven home: `/usr/share/maven` +* Java version: 11.0.23 +* Vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 +{% endhint %} + +### **Step 5: Update Environment Variables (Ubuntu - \~/.bashrc)** + +1. Open `.bashrc` and add env variables at the end of the file. + +```bash +vim ~/.bashrc +export SPARK_HOME=/opt/spark +export SPARK_MASTER=local[*] +export MAVEN_HOME=/home/ubuntu/apache-maven-3.8.8 +export ZINGG_HOME=/assembly/target +export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 +export PATH=$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin:$JAVA_HOME/bin +``` + +`` will be a directory where you clone the repository of the Zingg. Similarly, if you have installed spark on a different directory you can set **SPARK\_HOME** accordingly. + +{% hint style="success" icon="right-long" %} +Skip exporting `MAVEN_HOME` if multiple maven version are not required. +{% endhint %} + +2. Save/exit and do source .bashrc so that they reflect. + +```bash +source ~/.bashrc +``` + +3. Verify: + +```bash +echo $PATH +mvn --version +``` + +{% hint style="success" icon="right-long" %} +If you have already set up **JAVA\_HOME** and **SPARK\_HOME** in the steps before you don't need to do this again. +{% endhint %} + +### **Step 6: Compile The Zingg Repository** + +1. Make sure you are executing the following commands in the same terminal window where you saved the bashrc. Run the following to compile the Zingg Repository. + +```bash +git branch +``` + +2. Run the following to Compile the Zingg Repository + +```bash +mvn initialize +mvn clean compile package -Dspark=sparkVer +``` + +3. Run the following to Compile while skipping tests. + +```bash +mvn initialize +mvn clean compile package -Dspark=sparkVer -Dmaven.test.skip=true +``` + +{% hint style="success" icon="right-long" %} +Replace the `sparkVer` with the version of Spark you installed. For example, **-Dspark=3.5** you still face an error, include **-Dmaven.test.skip=true** with the above command. +{% endhint %} + +### **Step 7: If you have any issue with 'SPARK\_LOCAL\_IP' (Ubuntu)** + +1. Install **net-tools** using **sudo apt-get install -y net-tools** +2. Run `ifconfig` in the terminal, find the **IP address** and paste the same in **/opt/hosts** IP address of your Pc-Name + +### **Step 8: Run Zingg To Find Training Data** + +Run this script in the terminal opened in Zingg clones directory `./scripts/zingg.sh --phase findTrainingData --conf examples/febrl/config.json` + +_If everything is right, it should show Zingg banner._ + +### **Step 9: Run Zingg To label Data** + +Run this script in the terminal opened in Zingg clones directory `./scripts/zingg.sh --phase label --conf examples/febrl/config.json --properties-file config/zingg.conf` + +### **Step 10: Run Zingg To train model based on labeling** + +Run this script in the terminal opened in Zingg clones directory `./scripts/zingg.sh --phase train --conf examples/febrl/config.json --properties-file config/zingg.conf` + +### **Step 11: Run Zingg To prepare final output data** + +Run this script in the terminal opened in Zingg clones directory `./scripts/zingg.sh --phase match --conf examples/febrl/config.json --properties-file config/zingg.conf` + +Change the directory `cd /tmp/zinggOutput` (path provided in the configconfig file) to see the output files. diff --git a/docs/frequently-asked-questions/frequently-asked-questions.md b/docs/frequently-asked-questions/frequently-asked-questions.md new file mode 100644 index 000000000..bd02ca8df --- /dev/null +++ b/docs/frequently-asked-questions/frequently-asked-questions.md @@ -0,0 +1,126 @@ +--- +description: >- + Frequently asked questions about Zingg installation, configuration, + performance, and troubleshooting. +--- + +# Frequently Asked Questions + +### Training and labeling + +
+ +How many labeled pairs do I need before training? + +30 to 40 matching pairs is a strong starting point before your first `train` run. Label until you feel that your labeled examples represent all field types and data patterns in your schema. If accuracy needs improvement after your first match run, return to labeling; focus on the patterns or field combinations that appear to be missing or underrepresented. Ensure your training set is balanced with roughly equal numbers of match and non-match labels. + +
+ +
+ +findTrainingData is running very slowly. What should I do? + +Reduce `labelDataSampleSize`. The default is `0.5` which scans `50%` of your dataset. For large datasets, try `0.05` or `0.01`. You will get fewer candidate pairs per run, but the phase will complete faster. Run multiple rounds to accumulate enough labels. + +
+ +
+ +What happens to my trained model if I change a field definition? + +If you add, remove, or change the match type of a field, your existing trained model is no longer valid for that configuration. You must retrain. If you are using Zingg\ +Enterprise, use `reassignZinggId` after retraining to preserve existing Zingg IDs in downstream systems. + +
+ +### Match results and accuracy + +
+ +My match results have too many false positives. What is wrong? + +The similarity model has not seen enough non-match examples. Run `findTrainingData` and `label` again, focusing on pairs that look similar but are different entities. Label them as No Match. Retrain. Also check your field match types; fields that should use `EXACT`\ +but are set to `FUZZY` are a common source of false positives. + +
+ +
+ +My match results are missing matches I know should be there. What is wrong? + +Check blocking first. Run `verifyBlocking` to confirm your known matching pairs are being blocked. If they are in different blocks, the similarity model will never compare them regardless of how well it is trained. If blocking is fine, add more match labels for this type of pair and retrain. + +
+ +
+ +What is the difference between match and link? + +`match` finds duplicates within a single dataset. `link` matches records across two separate datasets that are each individually duplicate-free. Both use the same trained model and produce the same output columns. Neither is a subset of the other; they are two equal operations for different use cases. + +
+ +### Platform and installation + +
+ +Can I use Zingg without writing any code? + +Yes, using the JSON config file and CLI. Define your field definitions, data source, and output in config.json, then run each phase with `./zingg.sh --phase --conf config.json`. No Python code required. + +
+ +
+ +Does Zingg work on Windows? + +Zingg is tested primarily on Linux and macOS. On Windows, use the Zingg Docker image, which includes a pre-configured Linux environment. For production workloads use a managed Spark platform such as Databricks, Fabric, or GCP Dataproc. + +
+ +
+ +How do I upgrade Zingg? + +* For the Python package: `pip install --upgrade zingg`. +* For the Docker image: `docker pull zingg/zingg:` - replace `` with the release tag, for example `zingg/zingg:0.5.0`. Check `github.com/zinggAI/zingg/releases` for the latest version. + +After upgrading, your existing trained models remain compatible. You do not need to retrain unless you want to use new match types or features that are introduced in the new version. + +
+ +### How Zingg compares with other tools + +
+ +How is Zingg different from a record linkage library like Splink or Dedupe? + +Zingg runs natively on Apache Spark which gives it horizontal scalability for very large datasets. It is also designed for warehouse-native operation on platforms like Databricks, Fabric, and Snowflake without needing a separate Python environment. Zingg Enterprise adds persistent entity IDs, incremental matching, and deterministic matching features that are typically not available in open-source libraries. + +
+ +
+ +Is Zingg an MDM? + +Zingg Community is a DIY Master Data Management product that solves the entity matching and linking process. You can build an MDM on a data store of your choice using Zingg Community; bring your own warehouse, data lake, or database. + +Zingg Enterprise is a lakehouse- and warehouse-native MDM with advanced features: globally unique and persistent entity identifiers (Zingg ID), golden record survivorship, deterministic matching, incremental identity graph updates, and agentic stewardship. + +If you already have an MDM platform, Zingg can act as the entity resolution engine that feeds it - providing the matching and linking layer that most MDM tools require but do not include. + +
+ +
+ +Can I do entity resolution in TigerGraph or Neo4j instead of Zingg? + +Graph databases are a good fit for entity resolution if you have trusted, high-quality identifiers, such as passport IDs, SSNs, or other stable reference numbers through which edges can be defined between records. If your data has clean unique keys, a graph database can do entity resolution natively. + +Where graph databases struggle is fuzzy matching: handling typos, abbreviations, name variations, and data quality issues across millions of records. For that you need a purpose-built similarity model. + +Zingg and graph databases work well together. The recommended pattern is to use Zingg for the fuzzy entity resolution step, resolving ambiguous records into clusters, and then persisting Zingg's output (with Zingg IDs as node identifiers) in TigerGraph, Neo4j, or another graph database for relationship analysis, AML screening, KYC, and graph traversal queries. + +This combination gives you the best of both: Zingg handles the matching at scale; the graph database handles the relationship analysis and traversal. + +
diff --git a/docs/interpreting-results/explain-a-specific-cluster.md b/docs/interpreting-results/explain-a-specific-cluster.md new file mode 100644 index 000000000..8a17fd9be --- /dev/null +++ b/docs/interpreting-results/explain-a-specific-cluster.md @@ -0,0 +1,105 @@ +--- +description: >- + Run the explainOutput phase for a specific Zingg ID to see exactly how that + cluster was formed. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# Explain a Specific Cluster + +{% hint style="info" icon="right-long" %} +Enterprise only. Requires a completed match or `runIncremental` phase before running. +{% endhint %} + +The `explainOutput` phase takes a Zingg ID as input and returns the pair-level evidence for how\ +that cluster formed. You can see which record pairs were compared, what their similarity scores were, and how transitive matching connected records through intermediate pairs. + +{% tabs %} +{% tab title="Enterprise" %} +### Step 1: Import + +```python +from zingg.client import * +from zingg.pipes import * +from zinggEC.enterprise.common.EArguments import * +from zinggEC.enterprise.common.epipes import * +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition +from zinggEC.enterprise.common.ExplainArguments import * +from zinggEC.enterprise.common.EClientOptions import * +from zinggES.enterprise.spark.ESparkClient import * +``` + +### Step 2: Set up `ExplainArgument` + +Use the same arguments setup as your Configure Zingg notebook. All field definitions, pipes, `modelId`, and `zinggDir` must match the original match run exactly. + +```python +args = EArguments() +args.setModelId("your-model-id") +args.setZinggDir("/tmp/models") +``` + +### **Step 3: Set the Zingg ID to explain** + +Find Zingg IDs in the `ZINGG_ID` column of your match output. Replace the value below with the Zingg ID you want to explain. + +```python +args.setZinggId("ea67d79a-56a7-4431-ab55-d08bb3c10e2e") +``` + +### **Step 4: Run the explain phase** + +```python +options = ClientOptions([ ClientOptions.PHASE, "explainOutput" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +### **Step 5: Read the explain output** + +```python +explain_output = spark.read.parquet(f"{zinggDir}/{modelId}/explainOutput") +explain_output.show() +``` + +The output shows pair-level evidence for how the cluster formed. Each row is a record pair with their similarity score. + +{% hint style="success" icon="right-long" %} +**Read more**: `explainOutput` covers probabilistic matches only. Clusters formed through deterministic matching rules may return empty or partial results. For context on deterministic matching - [Configure Zingg](../running-zingg/configure-zingg.md). +{% endhint %} + +### Using the CLI instead of Python API + +If you prefer the CLI, create an `explainConfig.json` and run with the `--zinggid` flag: + +#### **`explainConfig.json`** + +```json +{ + "config" : "path_to_original_matching_config/config.json", + "explainOutput" : [ { + "name" : "outputExplain", + "format" : "csv", + "props" : { + "location" : "/tmp/zinggOutput_explain", + "delimiter" : ",", + "header" : true + } + } ] +} +``` + +#### CLI command + +```bash +./scripts/zingg.sh --phase explainOutput --zinggid ea67d79a-56a7-4431-ab55-d08bb3c10e2e --conf ./examples/febrl/explainConfig.json +``` +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +**CONTENT FOR THIS SECTION TO BE PROVIDED BY SONAL LATER** +{% endtab %} +{% endtabs %} diff --git a/docs/interpreting-results/explain-matches.md b/docs/interpreting-results/explain-matches.md new file mode 100644 index 000000000..698bdf843 --- /dev/null +++ b/docs/interpreting-results/explain-matches.md @@ -0,0 +1,33 @@ +--- +description: >- + Understand how a specific entity cluster was formed, which records matched, + which did not, and why. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# Explain Matches + +{% hint style="info" icon="right-long" %} +Enterprise only. The explain phase is not available in Community. +{% endhint %} + +Once you have run a match or `runIncremental` phase, you can use the `explainOutput` phase to look under the hood of any cluster. Provide a Zingg ID, and Zingg shows you exactly how that cluster formed, which record pairs were matched probabilistically, their individual scores, and which records did not directly match each other. + +This is useful for model validation, governance, explaining results to business stakeholders, human-in-the-loop reviews with domain experts, and building confidence in production pipelines. + +{% hint style="success" icon="right-long" %} +`explainOutput` currently covers probabilistic matches only. Deterministic matches are not included\ +in explain output at this time. Clusters formed primarily through deterministic matching rules may return empty results. + +If you get empty results, verify that the Zingg ID you are querying exists in your match output before running explain. +{% endhint %} + +{% hint style="success" icon="right-long" %} +**Read more**: + +* For the step-by-step explain phase task with code - [Explain a specific cluster](explain-a-specific-cluster.md) +* For output statistics - [Output statistics](output-statistics.md) +{% endhint %} diff --git a/docs/interpreting-results/interpret-output-scores.md b/docs/interpreting-results/interpret-output-scores.md new file mode 100644 index 000000000..1d5eb96ea --- /dev/null +++ b/docs/interpreting-results/interpret-output-scores.md @@ -0,0 +1,169 @@ +--- +description: >- + Every column Zingg adds to your match output, how scores are computed, and how + to decide which clusters need human review. +--- + +# Interpret Output Scores + +Every Zingg match or link output contains your original input fields plus a set of columns added by Zingg. These columns tell you which records have been resolved into the same entity, how confident the model is about each match, and (in Enterprise) provide a persistent identifier you can store downstream. + +This page defines every Zingg output column and shows how to use the scores to decide what to do with each cluster + +### Output columns - quick reference + +
ColumnAvailable inWhat it tells youJump to detail
Z_CLUSTERAll editionsWhich records resolved to the same entityZ_CLUSTER →
Z_MINSCOREAll editionsWeakest match confidence in the clusterZ_MINSCORE →
Z_MAXSCOREAll editionsStrongest match confidence in the clusterZ_MAXSCORE →
ZINGG_IDEnterprise onlyPersistent, globally unique entity GUIDZINGG_ID →
Z_SCOREEnterprise onlyPer-record-pair model confidence scoreZ_SCORE →
+ +### How Zingg computes scores + +For each field - `fname`, `lname`, `email`, and so on, Zingg computes multiple features and feeds them to a classifier. These features are different ways to compare strings: character-level differences, string length differences, positional weighting, and common-typo awareness. + +No individual feature is perfect, but the classifier finds the best-fit curve across all features and produces a final score. Key behaviors to understand: + +* The shorter string pair `ABCD`/`ABCE` will be less similar than `ABCDEF`/`ABCEEF` - length matters. +* Common typos (for example, `m` instead of `n`) are penalized less severely than unusual character swaps. +* Differences in the middle of a string are penalized more than prefix or suffix differences. +* The threshold is automatically optimized. You may see scores below the conventional 0.5; this is intentional. Zingg optimizes for both accuracy and recall, not just high scores. + +### Output columns - all editions + +#### **`Z_CLUSTER`** + +The most important column. All records sharing the same `Z_CLUSTER` value have been resolved to the same real-world entity. Group by `Z_CLUSTER`to collapse duplicates into a single golden record or link records across systems. + +In the Community version, `Z_CLUSTER` is non-persistent. It is reassigned fresh each time the match job runs. The same records may receive a different `Z_CLUSTER` on\ +the next run. + +In the Enterprise version, `Z_CLUSTER` is replaced by the persistent `Zingg ID`. See `Zingg ID` in the next section. + +{% hint style="success" icon="right-long" %} +**Read more**: [Z Cluster ID and Zingg ID](../zingg-concepts/z-cluster-and-zingg-id.md) +{% endhint %} + +#### `Z_MINSCORE` + +The lowest similarity score between any two records in the cluster. Indicates the confidence of the weakest link in the cluster. A cluster where `Z_MINSCORE` is `0` or very low means at least two records in that cluster matched weakly. Flag these for manual review. + +#### `Z_MAXSCORE` + +The highest similarity score between any two records in the cluster. Indicates the strongest match within the cluster. A high `Z_MAXSCORE` with a low `Z_MINSCORE` means the cluster\ +has both strong and weak matches - worth inspecting. + +#### `ZINGG_ID` (Enterprise) + +Globally unique, persistent identifier for each resolved entity. It does not change between runs, including incremental runs. Safe to store and reference in downstream systems. Replaces `Z_CLUSTER` in Enterprise output. + +{% hint style="success" icon="right-long" %} +**Read more**: For the full Zingg ID concept - [Zingg ID](/broken/pages/9QpDFW20AMt0UJ4cEW6b) +{% endhint %} + +#### `Z_SCORE` (Enterprise) + +The model confidence score is provided for each record pair. Higher values closer to 1.0 indicate a stronger likelihood that the records are a true match. Used alongside `Z_MINSCORE` and `Z_MAXSCORE` for threshold-based automated decisions. + +#### `Z_SOURCE` (Enterprise) + +Appears only in the link phase output. Identifies which source dataset each record came from. Use `Z_SOURCE` to trace each resolved record back to its origin system after linking two datasets together. + +{% hint style="success" icon="right-long" %} +**Read more**: For the link phase - [Link across datasets](../running-zingg/link-across-datasets.md) +{% endhint %} + +### Reading match output in code + +Reading match output is the same in Community and Enterprise; only the cluster column name differs. Community produces `Z_CLUSTER`. Enterprise produces `ZINGG_ID`. Replace the column name in your code accordingly. + +{% tabs %} +{% tab title="Community" %} +```python +from pyspark.sql.functions import col, count, avg + +output = spark.read.csv("/tmp/zinggOutput", header=True) + +output.groupBy("Z_CLUSTER") \ + .agg( + count("*").alias("records"), + avg(col("Z_MINSCORE").cast("double")).alias("avg_min"), + avg(col("Z_MAXSCORE").cast("double")).alias("avg_max") + ) \ + .orderBy("avg_min") \ + .show() +``` +{% endtab %} + +{% tab title="Enterprise" %} +```python +from pyspark.sql.functions import col, count, avg + +output = spark.read.csv("/tmp/zinggOutput", header=True) + +output.groupBy("ZINGG_ID") \ + .agg( + count("*").alias("records"), + avg(col("Z_MINSCORE").cast("double")).alias("avg_min"), + avg(col("Z_MAXSCORE").cast("double")).alias("avg_max") + ) \ + .orderBy("avg_min") \ + .show() +``` +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +**CONTENT FOR THIS SECTION TO BE GIVEN BY SONAL LATER** +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +Reading match output is the same in Community and Enterprise - only the cluster column name differs. Community produces `Z_CLUSTER`. Enterprise produces `ZINGG_ID`. Replace the column name in your code accordingly. +{% endhint %} + +### Using scores to decide what to do with clusters + +
+ +How do I set thresholds for automated decisions? + +Use `Z_MINSCORE` to gate automation. Clusters with high `Z_MINSCORE` are confident across every pair - these can flow directly to automated golden record creation. Clusters with low `Z_MINSCORE` have at least one weakly matched pair and should go to manual review. + +A common starting pattern: + +* `Z_MINSCORE` above 0.8 → auto-merge into golden record +* `Z_MINSCORE` between 0.4 and 0.8 → human review queue +* `Z_MINSCORE` below 0.4 → flag as suspect, often a false positive + +Tune these cutoffs based on your tolerance for false positives vs. missed reviews. The threshold values are not absolute - Zingg's automatic threshold optimization means scores below 0.5 can still be valid matches in your data. + +
+ +
+ +My cluster has records I don't think should be together. What do I do? + +Two possible causes: + +**Training data gap** - the similarity model has not seen enough non-match labels for pairs that look similar but represent different entities. Add `findTrainingData` runs and label more No Match pairs that resemble the false positive. + +**Field match type mismatch** - fields that should use `EXACT` are set to `FUZZY`. Date of birth, SSN, national ID, and tax IDs should always use `EXACT`. Fuzzy tolerance on these fields lets records with different identifier values match each other + +
+ +
+ +What does a score of 0 mean? + +A `Z_MINSCORE` of `0` does not mean the match is wrong. It means that at least two records in the cluster matched transitively - they were connected through a chain of\ +intermediate matches rather than directly. + +For example, Record A matches B with a score of 0.8. Record B matches C with a score of 0.7. All three go into the same cluster even if A and C have a low direct score. The cluster `Z_MINSCORE` will reflect the weakest direct pair comparison. + +Review clusters with `Z_MINSCORE` of `0` manually to confirm the full cluster is correct. + +
+ +{% hint style="success" icon="right-long" %} +**Read more**: + +* Z Cluster and Zingg ID - [Z Cluster ID and Zingg ID](../zingg-concepts/z-cluster-and-zingg-id.md) +* Explaining how a cluster formed - [Explain matches](explain-matches.md) +* Improving accuracy when results are wrong - [Improve accuracy](../tuning/improve-accuracy/) +{% endhint %} diff --git a/docs/interpreting-results/output-statistics.md b/docs/interpreting-results/output-statistics.md new file mode 100644 index 000000000..dbe7dbea3 --- /dev/null +++ b/docs/interpreting-results/output-statistics.md @@ -0,0 +1,157 @@ +--- +description: >- + Track match quality and volume across three levels: summary, cluster, and + record; using the Zingg stats output pipe. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# Output Statistics + +{% hint style="info" icon="right-long" %} +Enterprise only. Output statistics are generated when the stats output pipe is configured in [Configure Zingg](../running-zingg/configure-zingg.md). +{% endhint %} + +Zingg Enterprise generates match statistics at three levels alongside the match output. If you have ever asked 'how are deterministic rules performing?' or 'did my latest incremental run improve cluster quality?'; Output Statistics is your answer. + +Statistics reveal the connections that Zingg discovered among records within each cluster. During incremental runs, they show exactly how cluster counts change as records are inserted and updated. Specifically, statistics let you + +* See how dense or sparse your clusters are +* Understand how much of a cluster is driven by deterministic rules vs. probabilistic matching +* Identify highly central records (connectors) and outliers +* Track how clusters change across runs: growth, splits, merges, reassignments + +If the number of clusters changes disproportionately to the number of records updated or added, an alert could be triggered. + +{% tabs %} +{% tab title="Enterprise" %} +### **Configure the stats output pipe** + +The stats pipe is configured in Configure Zingg alongside your input and output pipes. The `$ZINGG_DYNAMIC_STAT_NAME` placeholder is replaced at runtime with `SUMMARY`, `CLUSTER`, or `RECORD` for the three statistics files. + +#### Python + +```python +statsOutputPipe = ECsvPipe("stats", "/tmp/febrlStats_$ZINGG_DYNAMIC_STAT_NAME") +statsOutputPipe.setHeader("true") +args.setOutputStats(statsOutputPipe) +``` + +#### JSON + +```json +{ + "outputStats" : { + "name" : "stats", + "format" : "csv", + "props" : { + "location" : "/tmp/zinggStats_$ZINGG_DYNAMIC_STAT_NAME", + "delimiter" : ",", + "header" : true + } + } +} +``` + +{% hint style="success" icon="right-long" %} +The `$ZINGG_DYNAMIC_STAT_NAME` placeholder is automatically substituted with the statistics type (`SUMMARY`, `CLUSTER`, or `RECORD`) and a timestamp. This ensures each phase run writes to a separate statistics file. If `outputStats` is not configured, Zingg will not write statistics but the run will proceed normally. +{% endhint %} +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +**CONTENT FOR THIS SECTION TO BE PROVIDED BY SONAL LATER** +{% endtab %} +{% endtabs %} + +### Three statistics levels + +{% tabs %} +{% tab title="Summary" %} +Summary statistics give a high-level view of the match run: + +* Total records processed +* Total clusters formed +* Total matched pairs +* Average cluster size +* Distribution of match scores + +### Summary statistics field names + +
FieldDescriptio
z_num_recordsTotal number of records processed in this run
z_num_clustersTotal number of clusters formed
z_time_stampTimestamp of the match or incremental run
+ +### Reading summary statistics + +Summary stats are written to the path configured with `$ZINGG_DYNAMIC_STAT_NAME` substituted with '`SUMMARY`'. + +```python +summary_path = (zinggDir + "/" + modelId + "/stats/SUMMARY_*") + +summary = spark.read.csv(summary_path, header=True) +summary.show() +``` +{% endtab %} + +{% tab title="Cluster" %} +Cluster-level statistics give detail per resolved entity: + +* Cluster ID (Zingg ID) +* Number of records in the cluster +* Minimum score within the cluster +* Maximum score within the cluster +* Whether the cluster changed since the last incremental run + +### Cluster statistics field names + +
FieldDescription
z_cluster_countNumber of records in the cluster (cluster size)
z_cluster_edgesTotal number of deterministic and probabilistic pairs among all records in the cluster
z_cluster_deterministic_edgesNumber of edges explained by deterministic rules
z_cluster_centralityEdge density: z_cluster_edges / (n*(n-1)/2). Values near 1 = dense cluster; lower = sparse
z_cluster_determinismProportion of connectivity driven by deterministic rules: z_cluster_deterministic_edges / (n*(n-1)/2)
+ +### Reading cluster statistics + +```python +cluster_path = (zinggDir + "/" + modelId + "/stats/CLUSTER_*") + +cluster_stats = spark.read.csv(cluster_path, header=True) +``` + +#### **Find large clusters with low density** + +```python +from pyspark.sql.functions import col + +cluster_stats \ + .filter((col("z_cluster_count") > 5) & (col("z_cluster_centrality") < 0.3)) \ + .show() +``` +{% endtab %} + +{% tab title="Record" %} +Record-level statistics give detail per individual record: + +* Record ID (your primary key) +* Zingg ID assigned +* Match score for this record within its cluster +* Whether this record is new (added in the latest incremental run) or existing + +### Record statistics field names + +
FieldDescription
z_record_edgesTotal number of other records this record matches (deterministically and probabilistically)
z_record_deterministic_edgesNumber of records this record matches deterministically
z_cluster_countNumber of records in the cluster this record belongs to
z_record_centralityHow central this record is within its cluster: z_record_edges / (n-1)
z_record_determinismProportion of this record's connectivity explained by deterministic rules: z_record_deterministic_edges / (n-1)
+ +### Reading record statistics + +```python +record_path = (zinggDir + "/" + modelId + "/stats/RECORD_*") + +record_stats = spark.read.csv(record_path, header=True) + +#Find connector records(high centrality) +#that may be bridging unrelated clusters +from pyspark.sql.functions import col + +record_stats \ + .filter(col("z_record_centrality") > 0.8) \ + .orderBy("z_record_centrality", ascending=False) \ + .show() +``` +{% endtab %} +{% endtabs %} diff --git a/docs/platform-guides/platform-guide-for-aws-emr.md b/docs/platform-guides/platform-guide-for-aws-emr.md new file mode 100644 index 000000000..c9583df1c --- /dev/null +++ b/docs/platform-guides/platform-guide-for-aws-emr.md @@ -0,0 +1,22 @@ +--- +description: >- + Run Zingg on AWS Elastic MapReduce for distributed Spark-based entity + resolution on AWS infrastructure. +--- + +# Platform Guide for AWS EMR + +AWS EMR provides managed Spark on AWS infrastructure. Zingg runs on EMR using the standard Python API for Community and EZingg for Enterprise. S3 is used for data storage and\ +model persistence using the `s3a://` path format. + +**CONTENT TO BE GIVEN BY NILANJAN/SIDDIK** + +{% tabs %} +{% tab title="Community" %} + +{% endtab %} + +{% tab title="Enterprise" %} + +{% endtab %} +{% endtabs %} diff --git a/docs/platform-guides/platform-guide-for-aws-glue.md b/docs/platform-guides/platform-guide-for-aws-glue.md new file mode 100644 index 000000000..4e0a9026b --- /dev/null +++ b/docs/platform-guides/platform-guide-for-aws-glue.md @@ -0,0 +1,672 @@ +--- +description: >- + End-to-end guide to running Zingg on AWS Glue from S3 bucket setup and IAM + configuration to your first match result. Covers Community (open source) and + Enterprise. +--- + +# Platform Guide for AWS GLUE + +AWS Glue provides serverless Spark on AWS. There are no servers to manage; you define your session configuration, Glue provisions the workers, and you pay only for what you use. Zingg runs on Glue Interactive Sessions using the standard Python API. S3 is used for all data input, output, model storage, and checkpointing. + +{% hint style="success" icon="right-long" %} +* Tested with AWS Glue version 5.0, worker type G.1X, Spark 3.5. +* AWS Glue Interactive Sessions restrict standard Jupyter widgets. Zingg's usual labeling widget does not render in Glue notebooks. This guide uses a CSV-based review workflow instead - candidate pairs are exported to S3 for offline labeling and read back into the session. This is covered in detail in Step 14. +{% endhint %} + +{% tabs %} +{% tab title="Community" %} +Uses `Arguments`, `FieldDefinition`, `CsvPipe`, and `ZinggWithSpark`. The full workflow runs in a single Glue Studio notebook. + +### AWS setup + +Complete these steps in the AWS Console before opening any notebook. + +#### **Step 1: Create an S3 bucket and upload JARs** + +Zingg on AWS Glue requires six JAR files uploaded to S3 before the session starts. These are injected into the Glue workers at session startup via the `%%configure` magic cell. + +Create an S3 bucket (for example `zingg-production-storage`) and upload all six JARs into a `/jars/` folder inside it. Also upload your data file (for example `test.csv`) directly to the bucket. + +**AWS Console:** + +1. Navigate to **S3 → Buckets → Create bucket**. +2. Give the bucket a globally unique name (for example `zingg-production-storage`). +3. Set the region to match where your Glue jobs will run (for example `us-east-1`). +4. Once created, upload the following JAR files into a `/jars/` folder: + +
JARPurpose
zingg-0.6.0.jarThe Zingg engine
zingg-common-client-0.6.0.jarCommon client
zingg-common-core-0.6.0.jarCommon core
zingg-spark-client-0.6.0.jarSpark client
zingg-spark-core-0.6.0.jarSpark core
zingg-common-infra-0.6.0.jarInfrastructure link
+ +Download all JARs from `github.com/zinggAI/zingg/releases`. Also upload your data file to the bucket root. + +_**IMAGE TO BE ADDED — S3 bucket view showing the\*\*\*\***** ****`/jars/`**** ****\*\*\*\*folder with all six Zingg JAR files listed. Tanwi to check with team for the screenshot.**_ + +#### **Step 2: Create an IAM role for Glue** + +AWS Glue Interactive Sessions require a dedicated IAM Role that allows the notebook to read and write S3, run Glue jobs, and pass the role to the Glue service. + +**Step 2a: Create the role** + +1. Navigate to **IAM Console → Roles → Create role**. +2. **Trusted entity type:** AWS service. +3. **Service or use case:** Glue. +4. Name the role (for example `zingg-glue-role`). + +**Step 2b: Attach managed policies** + +Search for and attach these four AWS managed policies: + +
PolicyPurpose
AmazonS3FullAccessRead and write data and models in S3
AWSGlueConsoleFullAccessManage Glue jobs via the Studio UI
AWSGlueServiceRoleRequired for Glue to run worker nodes
AmazonQDeveloperAccessEnables AI coding assistance (optional)
+ +**Step 2c: Attach an inline policy** + +Create an inline policy named `ZinggSessionPermissions` with the following JSON. Replace the `Resource` ARN in `AllowIAMPassRole` with your own role ARN. + +```json +{ + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "AllowIAMPassRole", + "Effect" : "Allow", + "Action" : "iam:PassRole", + "Resource" : "arn:aws:iam:::role/zingg-glue-role", + "Condition" : { + "StringLike" : { + "iam:PassedToService" : "glue.amazonaws.com" + } + } + }, + { + "Sid" : "ZinggBucketAccess", + "Effect" : "Allow", + "Action" : [ + "s3:GetObject", "s3:PutObject", "s3:ListBucket", "kms:Decrypt", + "kms:GenerateDataKey" + ], + "Resource" : [ + "arn:aws:s3:::your-bucket-name", + "arn:aws:s3:::your-bucket-name/*" + ] + }, + { + "Sid" : "GlueMetadata", + "Effect" : "Allow", + "Action" : [ "glue:Get*", "codewhisperer:GenerateRecommendations" ], + "Resource" : "*" + } + ] +} +``` + +_**IMAGE TO BE ADDED — IAM Role creation screen in the AWS Console showing the role name, attached managed policies, and the inline policy editor. Tanwi to check with team for the screenshot.**_ + +#### **Step 3: Create a Glue notebook and attach the IAM role** + +1. Navigate to **AWS Glue Studio → Notebooks**. +2. Choose **Jupyter Notebook** → **Interactive Session**. +3. Select the IAM role created in Step 2 (`zingg-glue-role`) from the dropdown. +4. Open the notebook. Confirm the kernel in the top right shows **Glue PySpark**. + +_**IMAGE TO BE ADDED — AWS Glue Studio Notebooks screen showing the IAM role dropdown with the\*\*\*\***** ****`zingg-glue-role`**** ****\*\*\*\*selected. Tanwi to check with team for the screenshot.**_ + +### Notebook 01: Set up Zingg + +Everything in this guide runs in a single Glue Studio notebook. The steps below follow the cell-by-cell execution order. + +#### **Step 4: Configure the Glue session (run this cell first)** + +This is the most important step for Glue. The `%%configure` magic cell must be **the very first cell you run** in a fresh notebook. It tells Glue to inject the Zingg JARs from S3 into every worker node and install the Zingg Python package before the Spark session starts. If you run any other cell first, the JARs will not be available. + +```python +% glue_version 5.0 +% worker_type G.1X +% number_of_workers 2 +% idle_timeout 2880 +%% configure { + "--extra-jars": "s3://your-bucket/jars/zingg-0.6.0.jar,s3://your-bucket/jars/zingg-common-client-0.6.0.jar,s3://your-bucket/jars/zingg-common-core-0.6.0.jar,s3://your-bucket/jars/zingg-spark-client-0.6.0.jar,s3://your-bucket/jars/zingg-spark-core-0.6.0.jar,s3://your-bucket/jars/zingg-common-infra-0.6.0.jar", + "--additional-python-modules": "zingg==0.6.0,tabulate,ipywidgets", + "--conf": "spark.serializer=org.apache.spark.serializer.KryoSerializer" +} +``` + +Replace `your-bucket` with your actual S3 bucket name throughout. + +{% hint style="success" icon="right-long" %} +`%%configure` must be the first cell executed. Running any other cell before this, including imports, starts the Glue session without the JARs. If this happens, stop the session (`%stop_session`) and start a fresh notebook. +{% endhint %} + +#### **Step 5: Initialise Spark and Glue contexts** + +AWS Glue requires a `GlueContext` alongside the standard Spark context. This is specific to Glue—other platforms do not need this. + +```python +import sys +from awsglue.transforms import * +from awsglue.context import GlueContext +from pyspark.context import SparkContext +from awsglue.job import Job + +sc = SparkContext.getOrCreate() +glueContext = GlueContext(sc) +spark = glueContext.spark_session +job = Job(glueContext) + +print("Spark and Glue contexts initialized.") +``` + +Verify Java is available: + +```python +print("Java Version:", + sc._gateway.jvm.java.lang.System.getProperty("java.version")) +``` + +#### **Step 6: Set the S3 checkpoint directory** + +Zingg uses a checkpoint directory to store intermediate Spark computation state. On Glue, this must be an S3 path - Glue workers are ephemeral and have no persistent local storage. + +```python +checkpoint_path = "s3://your-bucket/zingg_checkpoint/" +sc.setCheckpointDir(checkpoint_path) + +print(f"Spark checkpoint directory set to: {checkpoint_path}") +``` + +Verify it is set: + +```python +spark.sparkContext.getCheckpointDir() +``` + +{% hint style="success" icon="right-long" %} +Because Glue workers are temporary, storing checkpoints in S3 ensures Zingg can recover from any worker interruption during long-running training or match phases. Without this, a worker restart causes the entire job to fail. +{% endhint %} + +#### **Step 7: Set the model ID and S3 storage paths** + +`BUCKET` is your S3 bucket name. `modelId` is the unique name for this model run. `zinggDir` is where Zingg writes model files and training data. Use the same values for every step in this notebook. + +```python +BUCKET = "your-bucket-name" +modelId = "testModelFebrl" +zinggDir = f"s3://{BUCKET}/models" + +MARKED_DIR = f"s3://{BUCKET}/models/{modelId}/trainingData/marked/" +UNMARKED_DIR = f"s3://{BUCKET}/models/{modelId}/trainingData/unmarked/" + +print("S3 Paths Configured:") +print(f"Project Root: {zinggDir}") +print(f"Marked Data: {MARKED_DIR}") +print(f"Unmarked Data: {UNMARKED_DIR}") +``` + +`MARKED_DIR` and `UNMARKED_DIR` are derived automatically. Zingg writes candidate pairs to `UNMARKED_DIR` during `findTrainingData` and reads labeled pairs from `MARKED_DIR` during `train`. + +#### Step 8: Import libraries and set up helper functions + +```python +import pandas as pd +import numpy as np +import os, time, uuid, base64 +from tabulate import tabulate +from ipywidgets import widgets, interact, GridspecLayout +import pyspark.sql.functions as fn + +import boto3 +from botocore.exceptions import ClientError + +import zingg +from zingg.client import * +from zingg.pipes import * + +s3_client = boto3.client('s3') + +print("AWS and Zingg libraries imported successfully.") +``` + +Helper functions for managing training data and labels: + +```python +def cleanModel(): + """Clears previous training data from S3 + to restart model learning from scratch.""" + try: + prefixes = [ + f"models/{modelId}/trainingData/marked/", + f"models/{modelId}/trainingData/unmarked/" + ] + for prefix in prefixes: + paginator = s3_client.get_paginator('list_objects_v2') + for page in paginator.paginate(Bucket=BUCKET, Prefix=prefix): + if 'Contents' in page: + delete_keys = [ + {'Key': obj['Key']} + for obj in page['Contents']] + s3_client.delete_objects( + Bucket=BUCKET, + Delete={'Objects': delete_keys}) + print("Model directories cleaned.") + except Exception as e: + print(f"Error cleaning model: {str(e)}") + +def count_labeled_pairs(marked_pd): + """Returns positive, negative, and total + labeled pair counts.""" + if marked_pd.empty: + return 0, 0, 0 + n_total = len(np.unique(marked_pd['z_cluster'])) + n_positive = len(np.unique( + marked_pd[marked_pd['z_isMatch'] == 1]['z_cluster'])) + n_negative = len(np.unique( + marked_pd[marked_pd['z_isMatch'] == 0]['z_cluster'])) + return n_positive, n_negative, n_total +``` + +{% hint style="success" icon="right-long" %} +`boto3` is the AWS SDK for Python. It is used here for low-level S3 operations - scanning folders, reading labeled files, and deleting training data, that Spark cannot handle directly. `cleanModel()` uses a paginator to ensure all files are found even when a folder contains more than 1,000 objects. +{% endhint %} + +#### **Step 9: Build the arguments object** + +`Arguments` is the central configuration object. Every phase in this workflow reads from the same `args` instance. + +```python +args = Arguments() +args.setModelId(modelId) +args.setZinggDir(zinggDir) +print(args) +``` + +#### **Step 10: Preview your data** + +Read your CSV from S3 and preview it before configuring the Zingg pipes. + +```python +csv_path = f"s3://{BUCKET}/test.csv" +spark_df = spark.read.csv(csv_path, header=True, inferSchema=True) +schema_list = [ + "id", "fname", "lname", "stNo", "add1", "add2", "city", "state", + "areacode", "dob", "ssn" +] +spark_df = spark_df.toDF(*schema_list) + +print(f"Previewing data from {csv_path}:") +spark_df.show(10) +``` + +_**IMAGE TO BE ADDED - Glue notebook cell showing the\*\*\*\***** ****`spark_df.show(10)`**** ****\*\*\*\*output table with sample FEBRL records — the same entity appearing multiple times with field variations across rows. Tanwi to check with team for the screenshot.**_ + +#### **Step 11: Configure input and output pipes** + +`CsvPipe` connects Zingg to your S3 data. The schema string must match your dataset column names exactly. + +```python +schema = ( + "id string, fname string, " + "lname string, stNo string, " + "add1 string, add2 string, " + "city string, state string, " + "areacode string, dob string, " + "ssn string" +) + +inputPipe = CsvPipe("testFebrl", f"s3://{BUCKET}/test.csv", schema) +args.setData(inputPipe) + +output_path = f"s3://{BUCKET}/results/" +outputPipe = CsvPipe("resultOutput", output_path) +outputPipe.addProperty("header", "true") +args.setOutput(outputPipe) + +print("Input and output pipes configured.") +``` + +{% hint style="success" icon="right-long" %} +Zingg also supports Parquet and JSON output on S3. To push results downstream to Amazon Redshift, use the Redshift connector. + +For all connector formats → [Connect Relational Databases](../connect-your-data/connect-relational-databases.md) +{% endhint %} + +#### **Step 12: Define fields and match types** + +Every field in your input schema must appear in `fieldDefinition`. List the most important fields first — field order affects blocking quality. + +```python +id = FieldDefinition("id", "string", MatchType.EXACT) +fname = FieldDefinition("fname", "string", MatchType.FUZZY) +lname = FieldDefinition("lname", "string", MatchType.FUZZY) +stNo = FieldDefinition("stNo", "string", MatchType.FUZZY) +add1 = FieldDefinition("add1", "string", MatchType.FUZZY) +add2 = FieldDefinition("add2", "string", MatchType.FUZZY) +city = FieldDefinition("city", "string", MatchType.FUZZY) +state = FieldDefinition("state", "string", MatchType.FUZZY) +areacode = FieldDefinition("areacode", "string", MatchType.FUZZY) +dob = FieldDefinition("dob", "string", MatchType.EXACT) +ssn = FieldDefinition("ssn", "string", MatchType.EXACT) + +fieldDefs = [ + id, fname, lname, stNo, add1, add2, city, + state, areacode, dob, + ssn +] +args.setFieldDefinition(fieldDefs) +``` + +{% hint style="success" icon="right-long" %} +`FUZZY` handles variations like 'Jon' vs 'John' or 'St' vs 'Street'. `EXACT` requires a character-for-character match. `DONT_USE` excludes a field from matching but keeps it in output. + +**Read more**: For all match types → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +{% endhint %} + +#### **Step 13: Configure performance settings** + +`numPartitions` controls how data is distributed across Glue workers. `labelDataSampleSize` controls how much of the dataset is scanned when finding candidate pairs. + +```python +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.5) +``` + +{% hint style="danger" icon="right-long" %} +For a 2-worker G.1X cluster (4 vCPUs each), 4–8 partitions is a good starting point. Set `numPartitions` to approximately 2–3× your total worker vCPU count. For 1M+ records, reduce `labelDataSampleSize` to 0.01–0.05 to prevent the sampling phase from exhausting worker memory. +{% endhint %} + +### Notebook 01 continued: Find training data and label pairs + +{% hint style="danger" icon="right-long" %} +AWS Glue Interactive Sessions restrict standard Jupyter widgets. Zingg's standard `ipywidgets` labeling interface does not render in Glue notebooks. Instead, this guide exports candidate pairs as a CSV to S3 for offline review. You label each pair by entering 0, 1, or 2 in a spreadsheet, upload the file back to S3, and run a sync cell to feed the labels back into Zingg. The process is covered in Steps 15 and 16. +{% endhint %} + +#### **Step 14: Find candidate pairs** + +Zingg scans your dataset using the field rules defined in Step 12 and selects the most informative pairs for labeling. Candidate pairs are saved to `UNMARKED_DIR` in your S3 bucket. + +```python +options = ClientOptions([ + ClientOptions.PHASE, "findTrainingData"]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +#### **Step 15: Export candidate pairs for offline labeling** + +Initialise the label phase and pull candidate pairs into a local Pandas DataFrame: + +```python +options = ClientOptions([ClientOptions.PHASE, "label"]) +zingg = ZinggWithSpark(args, options) +zingg.init() + +candidate_pairs_pd = getPandasDfFromDs( + zingg.getUnmarkedRecords() +) + +if candidate_pairs_pd.shape[0] == 0: + print("No pairs found. Run findTrainingData first.") +else: + z_clusters = list(np.unique( + candidate_pairs_pd['z_cluster'] + )) + print(f"{len(z_clusters)} candidate pairs found for labeling") +``` + +Because standard Jupyter widgets do not work in Glue, export the candidate pairs as a vertical CSV to S3 for offline review. Each pair appears as a side-by-side block with a `>>> DECISION` row at the bottom where you enter `0` (No Match), `1` (Match), or `2` (Uncertain). + +```python +import pandas as pd, base64 + +ready_for_save = False +LABELS = {'Uncertain': 2, 'Match': 1, 'No Match': 0} + +n_pairs = int(candidate_pairs_pd.shape[0] / 2) +display_pd = candidate_pairs_pd.drop( + labels=['z_zid', 'z_prediction', + 'z_score', 'z_isMatch', 'z_zsource'], + axis=1, errors='ignore') + +vertical_review_data = [] + +for n in range(n_pairs): + candidate_left = display_pd.loc[2*n].to_list() + candidate_right = display_pd.loc[(2*n)+1].to_list() + + vertical_review_data.append({ + "Attribute": f"=== PAIR {n} ===", + "Record_A": "VALUE A", + "Record_B": "VALUE B"}) + + for i in range(display_pd.shape[1]): + column_name = display_pd.columns[i] + if column_name == 'z_cluster': + z_cluster = candidate_left[i] + vertical_review_data.append({ + "Attribute": column_name, + "Record_A": str(candidate_left[i]), + "Record_B": str(candidate_right[i])}) + + label_options = ", ".join( + [f"{k}({v})" for k, v in LABELS.items()]) + vertical_review_data.append({ + "Attribute": ">>> DECISION", + "Record_A": f"Choose: {label_options}", + "Record_B": ""}) + vertical_review_data.append({ + "Attribute": "-" * 20, + "Record_A": "", "Record_B": ""}) + +review_df = pd.DataFrame(vertical_review_data) +export_path = f"s3://{BUCKET}/review/pending_labels.csv" +spark.createDataFrame(review_df).coalesce(1)\ + .write.option("header", "true")\ + .mode("overwrite").csv(export_path) + +ready_for_save = True +print(f"Review sheet exported for {n_pairs} pairs to: {export_path}") +``` + +_**IMAGE TO BE ADDED — S3 console showing the\*\*\*\***** ****`/review/`**** ****folder with the exported**** ****`pending_labels.csv`**** ****\*\*\*\*part file ready for download. Tanwi to check with team for the screenshot.**_ + +{% hint style="danger" icon="right-long" %} +How to label the review sheet: + +1. Go to **S3 → your-bucket → review/** in the AWS Console and download the `part-00000-*.csv` file. +2. Open it in Excel or Google Sheets. +3. For each pair block, find the `>>> DECISION` row and enter your label in the `Record_B` column: `1` (Match), `0` (No Match), or `2` (Uncertain). +4. Save the file and upload it back to the same S3 path: `s3://your-bucket/review/` +5. Run Step 16 to feed the labels into Zingg. +{% endhint %} + +_**IMAGE TO BE ADDED — Example of the exported review CSV open in Excel showing two FEBRL records side by side in a vertical layout, with the\*\*\*\***** ****`>>> DECISION`**** ****row highlighted and a**** ****`1`**** ****entered in the**** ****`Record_B`**** ****\*\*\*\*column. Tanwi to check with team for the screenshot.**_ + +{% hint style="success" icon="right-long" %} +Target 30–40 match pairs and 30–40 non-match pairs before training. Repeat Steps 14–16 in a loop until you reach this target. Label until all field types and data variation patterns in your schema are covered. If accuracy needs improvement after the first match run, return to labeling and focus on patterns that are missing or underrepresented. +{% endhint %} + +#### **Step 16: Sync labeled pairs back to Zingg** + +After labeling the review sheet and uploading it back to S3, run this cell to read the labels and save them to the Zingg training data folder. + +```python +import pandas as pd, boto3, io + +if not ready_for_save: + print("Run the export cell first.") +else: + bucket_name = BUCKET + prefix = "review/" + s3_client = boto3.client('s3') + + # Auto-locate the CSV part file in the review folder + response = s3_client.list_objects_v2( + Bucket=bucket_name, Prefix=prefix) + + csv_key = None + if 'Contents' in response: + for obj in response['Contents']: + if obj['Key'].endswith('.csv'): + csv_key = obj['Key'] + break + + if not csv_key: + print(f"No CSV found in s3://{bucket_name}/{prefix}") + else: + print(f"Reading labels from: s3://{bucket_name}/{csv_key}") + + file_obj = s3_client.get_object( + Bucket=bucket_name, Key=csv_key + ) + labeled_df = pd.read_csv( + io.BytesIO(file_obj['Body'].read()), + sep=None, engine='python' + ) + labeled_df.columns = ( + ['Attribute', 'Record_A', 'Record_B'] + + list(labeled_df.columns[3:]) + ) + + print("Mapping labels to Zingg DataFrame...") + + for i in range(len(labeled_df)): + attr_val = str(labeled_df.iloc[i]['Attribute']) + if "=== PAIR" in attr_val: + pair_idx = int(attr_val.split(" ")[2]) + decision_idx = i + len(display_pd.columns) + 1 + if decision_idx < len(labeled_df): + user_input = labeled_df.iloc[ + decision_idx + ]['Record_B'] + if (pd.notna(user_input) + and str(user_input).strip() != ""): + label_int = int(float(user_input)) + target_cluster = candidate_pairs_pd.iloc[ + 2*pair_idx + ]['z_cluster'] + candidate_pairs_pd.loc[ + candidate_pairs_pd['z_cluster'] == target_cluster, + 'z_isMatch'] = label_int + + zingg.writeLabelledOutputFromPandas( + candidate_pairs_pd, args + ) + + marked_pd_df = getPandasDfFromDs( + zingg.getMarkedRecords() + ) + n_pos, n_neg, n_tot = count_labeled_pairs(marked_pd_df) + + print(f"Labels synchronized successfully.") + print(f"Total labeled: {n_tot} | Matches: {n_pos} | Non-matches: {n_neg}") + print("Run Steps 14-16 again if you need more pairs.") + ready_for_save = False +``` + +### Notebook 01 continued: Generate model documentation (optional) + +#### **Step 17: Generate model documentation** + +Run `generateDocs` after labeling to produce a model report showing field weights, training data quality, and precision and recall estimates. The HTML report is written to your S3 model directory and can be downloaded from the S3 console. + +```python +options = ClientOptions([ + ClientOptions.PHASE, "generateDocs"]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() + +DOCS_DIR = zinggDir + "/" + modelId + "/docs/" +s3_key = f"models/{modelId}/docs/model.html" + +s3_client = boto3.client('s3') +try: + s3_client.head_object(Bucket=BUCKET, Key=s3_key) + print(f"Model report is ready.") + print(f"Download from: s3://{BUCKET}/{s3_key}") +except Exception as e: + print(f"Documentation not found at expected S3 path. {e}") +``` + +{% hint style="danger" icon="right-long" %} +Unlike other platforms, Glue cannot render HTML inline in the notebook. Download `model.html` from the S3 console and open it in a browser to view the documentation. Navigate to **S3 → your-bucket → models → modelId → docs** to find the file. + +`generateDocs` is optional. Skip it if you have 30–40 matches and 30–40 non-matches and are confident in your labeling quality. +{% endhint %} + +### Notebook 01 continued: Train and match + +#### **Step 18: Train and match** + +`trainMatch` combines `train` and `match` into a single phase. Zingg builds a model from your labeled pairs and immediately applies it to the full dataset. This is the most compute-intensive step; it distributes the workload across all Glue workers. + +```python +options = ClientOptions([ + ClientOptions.PHASE, "trainMatch"]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() + +print(f"Training complete. Results saved to: s3://{BUCKET}/results/") +``` + +You can also run `train` and `match` as separate phases if you want to inspect the trained model before running the full dataset: + +```python +options = ClientOptions([ClientOptions.PHASE, "train"]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() + +options = ClientOptions([ClientOptions.PHASE, "match"]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +#### Step 19: View output + +Match output is written to `output_path` in your S3 bucket as distributed part files. Spark reads the entire folder and merges them automatically. + +```python +outputDF = spark.read.option( + "header", "false" +).csv(output_path) + +colNames = [ + "z_score", "z_cluster", "z_zid", + "id", "fname", "lname", "stNo", + "add1", "add2", "city", "state", + "areacode", "dob", "ssn" +] + +final_results = outputDF.toDF(*colNames) +final_results.orderBy("z_cluster").show(10, truncate=False) + +total_records = final_results.count() +unique_entities = final_results.select( + 'z_cluster' +).distinct().count() + +print(f"Total Records Processed: {total_records}") +print(f"Unique Entities Identified: {unique_entities}") +print(f"Redundancy Reduced by: " + f"{((total_records - unique_entities) / total_records) * 100:.2f}%") +``` + +_**IMAGE TO BE ADDED — Glue notebook cell showing\*\*\*\***** ****`final_results.orderBy("z_cluster").show(10)`**** ****output with resolved records grouped by**** ****`z_cluster`**** ****\*\*\*\*— two rows sharing the same cluster value visible in the output. Tanwi to check with team for the screenshot.**_ +{% endtab %} + +{% tab title="Enterprise" %} + +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Read more**: + +* Tune accuracy → [Improve Accuracy](../tuning/improve-accuracy/) +* Understand scores and set thresholds → [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) +* Set up incremental for production → [Run Incremental Matching](../running-zingg/run-incremental-matching.md) +{% endhint %} + +{% hint style="success" icon="right-long" %} +Download the notebooks used in this guide: + +* Community notebooks (NB01–04): Download the notebook used in this guide: `github.com/zinggAI/zingg/tree/main/examples/aws-glue` +* Enterprise notebooks — TO BE ADDED +{% endhint %} diff --git a/docs/platform-guides/platform-guide-for-azure-databricks.md b/docs/platform-guides/platform-guide-for-azure-databricks.md new file mode 100644 index 000000000..b9c5503ae --- /dev/null +++ b/docs/platform-guides/platform-guide-for-azure-databricks.md @@ -0,0 +1,773 @@ +--- +description: >- + End-to-end guide to running Zingg on Azure Databricks from cluster setup to + your first match result. Covers Community (open source) and Enterprise +--- + +# Platform Guide for Azure Databricks + +Databricks is a fully managed Spark environment that integrates seamlessly with Zingg. Both Community and Enterprise run on the same Databricks cluster. The difference is the libraries, class names, and the additional phases available in Enterprise. + +{% hint style="success" icon="right-long" %} +Tested with Databricks Runtime 16.4 LTS (Spark 3.5.2, Scala 2.12). Newer LTS versions with Spark 3.5 are compatible. +{% endhint %} + +{% hint style="success" icon="right-long" %} +**Read more**: For the Databricks connector config including Delta format, see [Connect Azure Databricks](../connect-your-data/connect-cloud-warehouses/connect-azure-databricks.md). +{% endhint %} + +{% tabs %} +{% tab title="Community" %} +Uses `Arguments`, `FieldDefinition`, `CsvPipe`, and `ZinggWithSpark`. The workflow runs across three notebooks. + +### Notebook 01: Set up Zingg + +#### Step 1: Create a cluster and install the Zingg JAR + +1. Go to **Compute** → **Create Cluster**. Name it `Zingg-Community`. +2. Set the runtime to a current LTS version for compatibility. +3. Download the latest Zingg JAR from `github.com/zinggAI/zingg/releases`. +4. Open the cluster → **Libraries** → **Install New** → **Upload JAR** → upload the file. + +#### **Step 2: Install the Zingg Python package** + +Open a notebook attached to the cluster and run: + +```python +%pip install zingg==0.6.0 +dbutils.library.restartPython() +``` + +**Verify the installation** + +```python +%pip show zingg +``` + +#### **Step 3: Set the model ID and storage path** + +`zinggDir` is where Zingg writes model files and training data. `modelId` is a unique name for this model run - Zingg uses it as the folder name under `zinggDir`. Use the same values across all notebooks in this workflow. + +```python +zinggDir = "/models" +modelId = "zinggTrial" +``` + +Update `zinggDir` to an `abfss://` or `s3a://` path if you want model files stored in cloud storage. `dbfs:/` paths are deprecated in Databricks—use Unity Catalog storage paths or external cloud storage instead. + +```python +MARKED_DIR = zinggDir + "/" + modelId + "/trainingData/marked/" +``` + +`MARKED_DIR` and `UNMARKED_DIR` are derived from your `zinggDir` and `modelId`. Zingg writes labeled training pairs to these paths during the `label` phase and reads them back during `train`. + +#### Step 4: Import libraries and set up helper functions + +```python +import pandas as pd +import numpy as np +import time +import uuid +from ipywidgets import widgets, interact, GridspecLayout +import base64 +import pyspark.sql.functions as fn + +from zingg.client import * +from zingg.pipes import * + +def count_labeled_pairs(marked_pd): + n_total = len(np.unique(marked_pd['z_cluster'])) + n_positive = len(np.unique(marked_pd[marked_pd['z_isMatch'] == 1]['z_cluster'])) + n_negative = len(np.unique(marked_pd[marked_pd['z_isMatch'] == 0]['z_cluster'])) + n_uncertain = len(np.unique(marked_pd[marked_pd['z_isMatch'] == 2]['z_cluster'])) + return n_positive, n_negative, n_uncertain, n_total +``` + +#### Step 5: Build the arguments object + +`Arguments` is the central configuration object. Every subsequent phase reads from the same `args` instance. + +```python +args = Arguments() +args.setModelId(modelId) +args.setZinggDir(zinggDir) +``` + +#### **Step 6: Configure performance settings** + +`numPartitions` controls how data is distributed across Spark workers. Set it to approximately 20–30× your worker vCPU count. `labelDataSampleSize` controls what fraction of the data is scanned when finding candidate pairs - reduce it if `findTrainingData` is slow. + +```python +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.5) + +spark.conf.set("spark.sql.adaptive.enabled", False) +``` + +{% hint style="success" icon="right-long" %} +For 100k records start with `labelDataSampleSize` between 0.1 and 0.5. For 1M+ records use 0.01 to 0.05. If `findTrainingData` takes too long, reduce by approximately 10× and try again. Disabling Adaptive Query Execution gives Zingg more predictable Spark behavior. +{% endhint %} + +#### Step 7: Connect your data + +The OS notebook uses `UCPipe` to connect to a Unity Catalog table. Replace `table` with your own `catalog.schema.tablename`. If your data is in a CSV file, use `CsvPipe` instead — see the hint below. + +```python +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.5) + +spark.conf.set("spark.sql.adaptive.enabled", False) +table = "zingg_catalog.input.test" +inputPipe = UCPipe("testFebrl65", table) +args.setData(inputPipe) +``` + +**Preview the Data** + +```python +df = spark.table(table) +display(df) +``` + +If your data is in a CSV file, use `CsvPipe` instead of `UCPipe`: + +```python +schema = ( + "rec_id string, fname string, " + "lname string, stNo string, " + "add1 string, add2 string, " + "city string, state string, " + "dob string, ssn string" +) + +inputPipe = CsvPipe("testFebrl", "/FileStore/tables/data.csv", schema) +args.setData(inputPipe) +``` + +Sample data to test with: `github.com/zinggAI/zingg/blob/main/examples/febrl120k/test.csv` + +#### Step 8: Configure output + +```python +outputTable = "zingg_catalog.output.febrlOutput" +outputPipe = UCPipe("resultFebrl", outputTable) +args.setOutput(outputPipe) +``` + +Output can also be a CSV or Parquet file. For all supported output formats → [Connect Azure Databricks](../connect-your-data/connect-cloud-warehouses/connect-azure-databricks.md). + +#### **Step 9: Define fields and match types** + +The order in which you list fields matters—put the most important fields first. Every field in your input schema must appear in `fieldDefinition`, either with a match type or as `DONT_USE`. + +```python +recId = FieldDefinition("recId", "STRING", MatchType.DONT_USE) +fName = FieldDefinition("fName", "STRING", MatchType.FUZZY) +lName = FieldDefinition("lName", "STRING", MatchType.FUZZY) +streetId = FieldDefinition("streetId", "STRING", MatchType.DONT_USE) +street = FieldDefinition("street", "STRING", MatchType.FUZZY) +locality = FieldDefinition("locality", "STRING", MatchType.FUZZY) +area = FieldDefinition("area", "STRING", MatchType.FUZZY) +areaCode = FieldDefinition("areaCode", "STRING", MatchType.FUZZY) +state = FieldDefinition("state", "STRING", MatchType.FUZZY) +dob = FieldDefinition("dob", "STRING", MatchType.FUZZY) +ssn = FieldDefinition("ssn", "STRING", MatchType.EXACT) + +args.setFieldDefinition([ + recId, fName, lName, streetId, street, + locality, area, areaCode, state, dob, + ssn +]) +``` + +{% hint style="success" icon="right-long" %} +`FUZZY` handles variations like 'Jon' vs 'John' or 'St' vs 'Street'. `EXACT` requires a character-for-character match. `DONT_USE` excludes a field from matching but keeps it in the output - use this for identifiers like record IDs. For all match types → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +{% endhint %} + +### Notebook 02: Find training data and label pairs + +#### **Step 10: Find candidate pairs** + +Zingg scans your data and selects the most informative pairs for labeling—edge cases where the model has the most to learn. Run this before labeling. + +```python +options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +#### Step 11: Load pairs for labeling + +```python +options = ClientOptions([ ClientOptions.PHASE, "label" ]) +zingg = ZinggWithSpark(args, options) +zingg.init() + +candidate_pairs_pd = getPandasDfFromDs(zingg.getUnmarkedRecords()) + +if candidate_pairs_pd.shape[0] == 0: + print("No pairs found. Run findTrainingData first.") +else: + z_clusters = list(np.unique(candidate_pairs_pd['z_cluster'])) + print(f"{len(z_clusters)} candidate pairs found for labeling") +``` + +#### Step 12: Label pairs in the widget + +A widget displays each candidate pair side by side. For each pair, select: + +* **Match**—these records represent the same real-world entity +* **No Match**—these records are different entities +* **Uncertain**—you cannot decide + +The widget code handles the display and state management. Run the cell to render it. + +_**IMAGE TO BE ADDED — Zingg labeling widget in a Databricks notebook showing two candidate records side by side with Match / No Match / Uncertain toggle buttons. Tanwi to check with team for a screenshot from a live notebook run.**_ + +{% hint style="success" icon="right-long" %} +Target 30–40 match pairs and 30–40 non-match pairs before training. Repeat Steps 10–12 until you reach this target. Label until all field types and data variation patterns in your schema are covered. If results need improvement after the first match run, return to labeling and focus on patterns that are missing or underrepresented. +{% endhint %} + +#### Step 13: Save labeled pairs + +After labeling pairs in the widget, run this cell to save your labels to the training data folder. + +```python +zingg.writeLabelledOutputFromPandas(candidate_pairs_pd, args) + +marked_pd_df = getPandasDfFromDs(zingg.getMarkedRecords()) +n_pos, n_neg, n_uncer, n_tot = count_labeled_pairs(marked_pd_df) +print(f"Out of total {n_tot} pairs,") +print(f"You have accumulated {n_pos} pairs labeled as positive matches.") +print(f"You have accumulated {n_neg} pairs labeled as not matches.") +print(f"You have accumulated {n_uncer} pairs labeled as uncertain.") +``` + +### Notebook 03: Train, match, and view output + +#### Step 14: Review labeled pairs before training + +Before training, review the pairs you have labeled to verify quality. This uses the `updateLabel` phase to surface your marked records for inspection. + +```python +options = ClientOptions([ ClientOptions.PHASE, "updateLabel" ]) +zingg = ZinggWithSpark(args, options) +zingg.init() + +markedRecords = getPandasDfFromDs(zingg.getMarkedRecords()) +display(markedRecords) +``` + +#### **Step 15: Train the model** + +Zingg builds the blocking and similarity models from your labeled pairs and persists them to `zinggDir/modelId`. Once trained, this model can be reused on new data without retraining. + +```python +options = ClientOptions([ ClientOptions.PHASE, "train" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +#### **Step 16: Run the match** + +Applies the trained model to your full dataset and writes resolved clusters to the output location configured in Step 8. + +```python +options = ClientOptions([ ClientOptions.PHASE, "match" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +#### Step 17: View output + +```python +df = spark.table(outputTable) +display(df) +print(df.count()) +``` + +_**IMAGE TO BE ADDED — match output table in Databricks showing resolved records with\*\*\*\***** ****`Z_CLUSTER`**** ****column visible alongside original fields. Ideally highlight two rows sharing the same**** ****`Z_CLUSTER`**** ****\*\*\*\*to show they have been resolved to the same entity. Tanwi to check with team for screenshot from a live notebook run.**_ + +{% hint style="success" icon="right-long" %} +Records sharing the same `Z_CLUSTER` value have been resolved to the same real-world entity. `Z_MINSCORE` is the weakest match confidence within the cluster. `Z_MAXSCORE` is the strongest. For full output column definitions → [Interpret Output Scores](../interpreting-results/interpret-output-scores.md). +{% endhint %} + +### Notebook 04: Generate model documentation (optional) + +Run `generateDocs` after labeling to produce readable HTML documentation of your training data—both matched and non-matched pairs. Run it before training to inspect data quality, or share the output with subject matter experts to validate labels before committing to train. + +```python +options = ClientOptions([ ClientOptions.PHASE, "generateDocs" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() + +DOCS_DIR = zinggDir + "/" + modelId + "/docs/" +displayHTML(open(DOCS_DIR + "model.html", 'r').read()) +``` + +_**IMAGE TO BE ADDED —**** ****`generateDocs`**** ****HTML output rendered inside a Databricks notebook showing labeled pair examples. Tanwi to check with team for screenshot from a live notebook run. Even a small portion of the rendered HTML is sufficient — it tells the reader what to expect before they run it. Place: below the**** ****`displayHTML`****\*\*\*\*\*\*\*\* \*\*\*\*line.**_ +{% endtab %} + +{% tab title="Enterprise" %} +Uses `EArguments`, `EFieldDefinition`, `ECsvPipe`, `EZinggWithSpark`. Seven notebooks, each covering one phase. + +Enterprise adds blocking model configuration, a primary key for incremental matching, stats output, deterministic matching rules, pass-through expressions, the `runIncremental` phase, and the `explain` phase. + +{% hint style="warning" icon="right-long" %} +Enterprise requires a Zingg licence and the `zinggEC` and `zinggES` packages. [Contact Zingg to get access](https://www.zingg.ai/company/contact/contact). +{% endhint %} + +### Notebook 01: Set up Zingg + +#### Step 1: Create a cluster and install Enterprise JARs + +1. Go to **Compute** → **Create Cluster**. Name it `Zingg-Enterprise`. +2. Set the runtime to a current LTS version. +3. Create a managed Volume inside your catalog schema. +4. Upload `zingg-enterprise-spark-0.6.0.jar` and `zingg_license.jar` to the Volume. +5. Open the cluster → **Libraries** → **Install New** → **Volumes** → navigate to: `/Volumes/catalog_name/schema_name/volume_name/zingg-enterprise-spark-0.6.0.jar` +6. Repeat for `zingg_license.jar`. + +_**IMAGE TO BE ADDED — Databricks cluster Libraries tab showing the Enterprise JAR files installed from a Volume path. Tanwi to check with team for screenshot from a live Enterprise cluster setup.**_ + +#### Step 2: Verify all three packages are installed + +```python +!pip show zingg +!pip show zinggEC +!pip show zinggES +``` + +{% hint style="info" icon="right-long" %} +All three must show as installed: `zingg` (Community base), `zinggEC` (Enterprise), `zinggES` (Enterprise Plus). If any show as not found, install the corresponding `.whl` file from the cluster Libraries tab using the wheels provided in your Enterprise package. +{% endhint %} + +#### Step 3: Set the checkpoint directory + +```python +spark.sparkContext.setCheckpointDir("Files") +``` + +#### Step 4: Import libraries + +```python +import pandas as pd +import numpy as np +import os, time, uuid +from ipywidgets import widgets, interact, GridspecLayout +import base64 +import pyspark.sql.functions as fn +from zinggEC.enterprise.common.ApproverArguments import * +from zinggEC.enterprise.common.IncrementalArguments import * +from zinggEC.enterprise.common.epipes import * +from zinggEC.enterprise.common.EArguments import * +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition +from zinggES.enterprise.spark.ESparkClient import * +from zingg.client import * +from zingg.pipes import * +``` + +#### Step 5: Set the model ID and storage path + +`files_dir` is the root path for all input, output, and model storage. `zingg_dir` is where Zingg writes model files. `model_id` is the unique name for this run. + +```python +files_dir = ( + "abfss://@onelake.dfs.fabric.microsoft.com//" + "Files" +) +zingg_dir = files_dir + "/zingg" +model_id = "zinggModel" +MARKED_DIR = zingg_dir + "/" + model_id + "/trainingData/marked/" +UNMARKED_DIR = zingg_dir + "/" + model_id + "/trainingData/unmarked/" +``` + +Replace `files_dir` with your actual Databricks external storage path (`abfss://` for ADLS or `s3a://` for S3). `dbfs:/` paths are deprecated—use external storage. + +#### **Step 6: Build the Enterprise arguments object** + +`EArguments` is the Enterprise equivalent of `Arguments`. `setBlockingModel` sets the blocking strategy. `DEFAULT` suits most datasets—use `WIDER` if you know matching pairs are being missed. + +```python +args = EArguments() +args.setModelId(model_id) +args.setZinggDir(zingg_dir) +args.setBlockingModel("DEFAULT") +``` + +#### Step 7: Configure performance settings + +```python +args.setNumPartitions(32) +spark.conf.set("spark.sql.adaptive.enabled", False) +``` + +{% hint style="info" icon="right-long" %} +Set `numPartitions` to approximately 20–30× your worker vCPU count. For a 4-node cluster with 8 vCPUs each, 32 is a good starting point. `labelDataSampleSize` is set in Notebook 03—see that notebook for guidance. +{% endhint %} + +#### **Step 8: Connect your data** + +Enterprise uses `ECsvPipe` for CSV and Parquet, or `UCPipe` for Unity Catalog tables. + +```python +schema = ( + "id string, fname string, lname string, " + "streetId string, street string, locality string, " + "area string, areacode string, state string, " + "dob string, ssn string" +) + +input_path = files_dir + "/input/your_data.csv" +inputPipe = ECsvPipe("testFebrl", input_path, schema) +args.setData(inputPipe) +``` + +**Preview your data** + +```python +data = spark.read.csv(input_path, header=True) +display(data) +``` + +#### Step 9: Configure output + +```python +output_path = files_dir + "/output/" + model_id +outputPipe = ECsvPipe("resultOutput", output_path) +outputPipe.addProperty("header", "true") +args.setOutput(outputPipe) +``` + +#### **Step 10: Configure stats output** + +Stats output is an Enterprise feature. Zingg replaces `$ZINGG_DYNAMIC_STAT_NAME` at runtime with `SUMMARY`, `CLUSTER`, or `RECORD`, writing three separate stats files per run. + +```python +stats_path = (files_dir + "/statsOutput$ZINGG_DYNAMIC_STAT_NAME") +statsOutputPipe = ECsvPipe("stats", stats_path) +statsOutputPipe.addProperty("header", "true") +args.setOutputStats(statsOutputPipe) +``` + +{% hint style="success" icon="right-long" %} +**Read more:** If `outputStats` is not configured, Zingg skips stats writing and the run proceeds normally. For stats field definitions → [Output Statistics](../interpreting-results/output-statistics.md). +{% endhint %} + +#### **Step 11: Define fields with `EFieldDefinition`** + +`EFieldDefinition` is the Enterprise equivalent of `FieldDefinition`. Mark your primary key field with `setPrimaryKey(True)` — this is required for `runIncremental` to identify records across runs. Put the most important fields first. + +```python +recId = EFieldDefinition("id", "string", MatchType.DONT_USE) +recId.setPrimaryKey(True) +fname = EFieldDefinition("fname", "string", MatchType.FUZZY) +lname = EFieldDefinition("lname", "string", MatchType.FUZZY) +streetId = EFieldDefinition("streetId", "string", MatchType.FUZZY) +street = EFieldDefinition("street", "string", MatchType.FUZZY) +locality = EFieldDefinition("locality", "string", MatchType.FUZZY) +area = EFieldDefinition("area", "string", MatchType.FUZZY) +areacode = EFieldDefinition("areacode", "string", MatchType.FUZZY) +state = EFieldDefinition("state", "string", MatchType.FUZZY) +dob = EFieldDefinition("dob", "string", MatchType.FUZZY) +ssn = EFieldDefinition("ssn", "string", MatchType.FUZZY) + +fieldDefs = [ + recId, fname, lname, streetId, street, + locality, area, areacode, state, dob, + ssn +] +args.setFieldDefinition(fieldDefs) +``` + +#### **Step 12: Deterministic matching and pass-through (optional)** + +Deterministic matching short-circuits probabilistic scoring — records matching all fields in a condition are always matched regardless of similarity scores. Pass-through excludes specific records from matching while still including them in output with their own Zingg ID. + +**Deterministic conditions** + +```python +dm1 = DeterministicMatching('fname', 'streetId', 'area') +dm2 = DeterministicMatching('fname', 'streetId', 'lname') +args.setDeterministicMatchingCondition(dm1, dm2) +``` + +**Pass-through expression** + +```python +args.setPassthroughExpr("fname = 'matilda'") +``` + +{% hint style="success" icon="right-long" %} +**Read more**: + +* For deterministic matching concepts → [Configure Zingg](../running-zingg/configure-zingg.md) +* For pass-through → [Pass Through](../zingg-concepts/pass-through.md) +{% endhint %} + +### Notebook 02: Remove stopwords (Optional) + +Stopwords are high-frequency words that appear across many records but carry no matching signal — words like `pvt`, `ltd`, `st`, `ave`. Removing them improves blocking and similarity accuracy on address and company name fields. + +#### **Step 13: Generate stopword recommendations** + +Zingg analyses the specified column and returns a list of high-frequency words it recommends treating as stopwords. Run this once per column you want to clean. + +```python +stopwordcolumn = "street" +args.setColumn(stopwordcolumn) +options = ClientOptions([ ClientOptions.PHASE, "recommend" ]) +zingg = EZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +#### Step 14: Review recommendations + +```python +stopwordsForStreet = spark.read.csv( + zingg_dir + "/" + model_id + "/stopWords/" + stopwordcolumn +) +stopwordsForStreet.show() +``` + +_**IMAGE TO BE ADDED — Databricks notebook showing the stopwords output table with word and frequency columns. Tanwi to check with team for screenshot from a live notebook run. A simple table with 10–15 rows is sufficient that tells the reader what the recommendation output looks like before they run it.**_ + +#### **Step 15: Apply stopwords to the field definition** + +Review the recommendations. You can use them as-is or edit the list — add or remove words that matter for your specific dataset. + +```python +street.setStopWords(zingg_dir + "/" + model_id + "/stopWords/" + stopwordcolumn) + +print(args.getArgs()) +``` + +{% hint style="success" icon="right-long" %} +**Read more:** Skip this notebook on the first run. Return to it if match accuracy on text-heavy fields needs improvement. For the full stopwords guide → [Remove Stopwords](../tuning/improve-accuracy/remove-stopwords-optional.md) +{% endhint %} + +### Notebook 03: Find training data and label pairs + +#### **Step 16: Set `labelDataSampleSize`** + +`labelDataSampleSize` controls how much of your data is scanned when finding candidate pairs. + +```python +args.setLabelDataSampleSize(0.1) +``` + +{% hint style="info" icon="right-long" %} +For 100k records use 0.1–0.5. For 1M records use 0.01–0.05. If `findTrainingData` takes too long, reduce by approximately 10× and try again. +{% endhint %} + +#### Step 17: Find candidate pairs + +```python +options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +zingg = EZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +#### Step 18: Load pairs for labeling + +```python +options = ClientOptions([ ClientOptions.PHASE, "label" ]) +zingg = EZinggWithSpark(args, options) +zingg.init() + +candidate_pairs_pd = getPandasDfFromDs(zingg.getUnmarkedRecords()) + +if candidate_pairs_pd.shape[0] == 0: + print("No pairs found. Run findTrainingData first.") +else: + z_clusters = list(np.unique(candidate_pairs_pd['z_cluster'])) + print(f"{len(z_clusters)} candidate pairs found for labeling") +``` + +#### Step 19: Label pairs in the widget + +The Enterprise widget shows one pair at a time with Prev and Next navigation. For each pair select `Match`, `No Match`, or `Uncertain`. Labels are saved directly to the `DataFrame` as you click. + +_**IMAGE TO BE ADDED — Enterprise labeling widget in a Databricks notebook: two records displayed in a table, Match / No Match / Uncertain toggle buttons, Prev and Next navigation. Tanwi to check with team for screenshot from a live Enterprise notebook run. If the OS and Enterprise widgets look identical, the same screenshot can be reused.**_ + +{% hint style="info" icon="right-long" %} +Target 30–40 match pairs and 30–40 non-match pairs before training. Repeat Steps 17–19 until all field types and data patterns are represented. If accuracy needs improvement after the first match run, return here and focus on patterns that are underrepresented. +{% endhint %} + +#### Step 20: Save labeled pairs + +```python +zingg.writeLabelledOutputFromPandas(candidate_pairs_pd, args) + +marked_pd_df = getPandasDfFromDs(zingg.getMarkedRecords()) +n_pos, n_neg, n_uncer, n_tot = count_labeled_pairs(marked_pd_df) +print(f"Out of total {n_tot} pairs,") +print(f"You have accumulated {n_pos} pairs labeled as positive matches.") +print(f"You have accumulated {n_neg} pairs labeled as not matches.") +print(f"You have accumulated {n_uncer} pairs labeled as uncertain.") +``` + +### Notebook 04: Generate model documentation + +Run `generateDocs` after labeling and before training to inspect training data quality. Produces readable HTML documentation of your labeled pairs—both matches and non-matches. + +```python +options = ClientOptions([ + ClientOptions.PHASE, "generateDocs"]) +zingg = EZinggWithSpark(args, options) +zingg.initAndExecute() + +DOCS_DIR = zingg_dir + "/" + model_id + "/docs/" + +model_doc = spark.read.text(DOCS_DIR + "model/part-*") +model_html = "\n".join(r.value for r in model_doc.collect()) +displayHTML(model_html) + +data_doc = spark.read.text(DOCS_DIR + "data/part-*") +data_html = "\n".join(r.value for r in data_doc.collect()) +displayHTML(data_html) +``` + +_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\* \*\*\*\*output rendered inside a Databricks notebook showing labeled pair examples in HTML. Tanwi to check with team for screenshot from a live notebook run. Can reuse the OS version if the output looks the same.**_ + +### Notebook 05: Train and match + +#### **Step 21: Train the model** + +```python +options = ClientOptions([ ClientOptions.PHASE, "train" ]) +zingg = EZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +#### Step 22: Run the match + +```python +options = ClientOptions([ ClientOptions.PHASE, "match" ]) +zingg = EZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +#### Step 23: View output + +```python +outputDF = spark.read.csv(output_path, header=True) +display(outputDF) +print(outputDF.count()) +``` + +_**IMAGE TO BE ADDED — Enterprise match output in Databricks showing\*\*\*\***** ****`ZINGG_ID`**** ****column alongside resolved records. Show two rows with the same**** ****`ZINGG_ID`**** ****\*\*\*\*to illustrate entity resolution. Tanwi to check with team for screenshot from a live notebook run.**_ + +{% hint style="success" icon="right-long" %} +**Read more**: Enterprise output includes `ZINGG_ID` — a globally unique, persistent identifier for each resolved entity. Unlike `Z_CLUSTER` in Community, `ZINGG_ID` does not change between runs including incremental runs. + +* For output column definitions → [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) +* For Zingg ID lifecycle → [Zingg ID](/broken/pages/9QpDFW20AMt0UJ4cEW6b) +{% endhint %} + +### Notebook 06: Run incremental matching + +After the initial match, use `runIncremental` to update the identity graph with new or changed records without retraining the model. New records that match an existing cluster inherit its `ZINGG_ID`. Records that do not match any existing cluster receive a new `ZINGG_ID`. + +#### **Step 24: Configure incremental input** + +`IncrementalArguments` wraps your base `args` and adds the incremental data source and a temporary output path. + +```python +incrArgs = IncrementalArguments() +incrArgs.setParentArgs(args) + +incremental_input_path = files_dir + "/input/incr.csv" +incrPipe = ECsvPipe("testFebrlIncr", incremental_input_path, schema) +incrArgs.setIncrementalData(incrPipe) + +tmp_output_path = files_dir + "/output/temp" +outputTmpPipe = ECsvPipe("outputTemp", tmp_output_path) +outputTmpPipe.addProperty("header", "true") +incrArgs.setOutputTmp(outputTmpPipe) +``` + +{% hint style="info" icon="right-long" %} +`setOutputTmp` specifies a temporary path where Zingg writes intermediate results before merging them into the main output. It must be different from your main output path. +{% endhint %} + +#### Step 25: Run incremental + +```python +options = ClientOptions([ ClientOptions.PHASE, "runIncremental" ]) +zingg = EZinggWithSpark(incrArgs, options) +zingg.initAndExecute() +``` + +#### Step 26: View updated output + +```python +outputDF = spark.read.csv(output_path, header=True) +display(outputDF) +print(outputDF.count()) +``` + +{% hint style="success" icon="right-long" %} +**Read more**: For the full incremental matching guide including cluster merge and reassignment behaviour → [Run Incremental Matching](../running-zingg/run-incremental-matching.md) +{% endhint %} + +### Notebook 07: Explain output + +The explain phase shows how a specific cluster was formed, which record pairs contributed, their similarity scores, and how transitive matching connected records through intermediate pairs. Use this for governance, model validation, and sharing evidence with domain experts. + +#### **Step 27: Import explain libraries** + +```python +from zinggEC.enterprise.common.ExplainArguments import* +from zinggEC.enterprise.common.EClientOptions import* +``` + +#### **Step 28: Set the cluster to explain** + +Find a `ZINGG_ID` from your match output that you want to investigate. Copy it into `zingg_id` below. + +```python +explain_output = files_dir + "/output/explainOutput" +zingg_id = "7b73c8f1-1b39-4314-bb5f-7f5674183cc3" +``` + +#### Step 29: Run the explain phase + +```python +explainArgs = ExplainArguments() +explainArgs.setParentArgs(args) + +explainPipe = ECsvPipe("outputexplain", explain_output) +explainArgs.setExplainOutput(explainPipe) + +explainOptions = EClientOptions( + [ EClientOptions.PHASE, "explain", EClientOptions.ZINGG_ID, zingg_id ] +) +zinggExplain = EZingg(explainArgs, explainOptions) +zinggExplain.initAndExecute() +``` + +#### Step 30: View explain output + +```python +outputDF = spark.read.csv(explain_output, header=True) +display(outputDF) +print(outputDF.count()) +``` + +_**IMAGE TO BE ADDED — explain output table in Databricks showing\*\*\*\***** ****`pk1`****,**** ****`pk2`****\*\*\*\*, and similarity score columns for matched pairs within the cluster. Tanwi to check with team for screenshot from a live notebook run. A small 5–10 row output table is sufficient.**_ + +{% hint style="success" icon="right-long" %} +**Read more**: Each row in the output represents a matched record pair within the cluster — `pk1` and `pk2` are the primary keys of the two records, with their similarity score. + +For the full explain guide → [Explain a Specific Cluster](../interpreting-results/explain-a-specific-cluster.md) +{% endhint %} +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +Download the notebooks used in this guide: + +* Community notebooks (NB01–04): `github.com/zinggAI/zingg/tree/main/examples/databricks` +* Enterprise notebooks (NB01–07): included in your Zingg Enterprise package +{% endhint %} diff --git a/docs/platform-guides/platform-guide-for-gcp-dataproc.md b/docs/platform-guides/platform-guide-for-gcp-dataproc.md new file mode 100644 index 000000000..9ab8951e7 --- /dev/null +++ b/docs/platform-guides/platform-guide-for-gcp-dataproc.md @@ -0,0 +1,486 @@ +--- +description: >- + End-to-end guide to running Zingg on Google Cloud Dataproc from GCS storage + setup and cluster creation to your first match result. Covers Community (open + source) and Enterprise. +--- + +# Platform Guide for GCP Dataproc + +Combining Zingg with Google Cloud gives you elastic Spark scale via Dataproc, flexible storage via GCS, and a managed JupyterLab workspace via the Component Gateway. Spin up a cluster when you need it and shut it down when the job is done. + +{% hint style="success" icon="right-long" %} +Tested with Dataproc image version 2.2-debian12 (Spark 3.5). The `n2-standard-4` machine type with 16GB RAM per node is the recommended minimum for Zingg's training phases. +{% endhint %} + +{% tabs %} +{% tab title="Community" %} +Uses `Arguments`, `FieldDefinition`, `CsvPipe`, and `ZinggWithSpark`. Runs on a standard Dataproc cluster with JupyterLab. + +### Step 1: Download JARs and prepare your GCS bucket + +Zingg on GCP requires three JARs to bridge Spark with Google Cloud services. Download these to your local machine before creating the cluster. + +
JARPurposeDownload
zingg-0.6.0.jarThe Zingg enginegithub.com/zinggAI/zingg/releases
spark-3.5-bigquery-0.44.1.jarBigQuery connectorgithub.com/GoogleCloudDataproc/spark-bigquery-connector
gcs-connector-hadoop3-latest.jarGCS connectordocs.cloud.google.com/dataproc/docs/concepts/connectors/cloud-storage
+ +Create a GCS bucket and upload the JARs and your dataset. You can do this from the Cloud Console or the `gcloud` CLI. + +#### **Cloud Console** + +1. Navigate to **Cloud Storage → Buckets → Create**. +2. Name your bucket with a globally unique identifier (for example `zingg-production-storage`). +3. Set the region to match where your Dataproc cluster will run (for example `us-central1`). +4. Upload the three JARs and your data file (for example `customers.csv`) to the bucket. + +_**IMAGE TO BE ADDED - GCS bucket creation screen in the Google Cloud Console showing bucket name, region selector, and upload interface. Tanwi to check with team for screenshot from a live GCS console.**_ + +#### gcloud CLI + +```bash +BUCKET="zingg-production-storage" + +gcloud storage buckets create gs://$BUCKET \ + --location=us-central1 + +gsutil cp *.jar gs://$BUCKET/ +gsutil cp customers.csv gs://$BUCKET/ +``` + +### Step 2: Create a Dataproc cluster + +The cluster must be created with the three JARs injected via `spark.jars`. This is the critical configuration step; without it, Zingg cannot run on the cluster. + +#### **Cloud Console** + +1. Search for **Dataproc** and select **Create Cluster on Compute Engine**. +2. Under **Optional components**, check **Jupyter Notebook**. +3. Under **Component Gateway**, check **Enable component gateway**. +4. Set **Master** and **Worker** nodes to `n2-standard-4` with 100GB boot disk. +5. Scroll to **Properties** and add: + * **Key:** `spark.jars` + * **Value:** `gs://YOUR_BUCKET/zingg-0.6.0.jar,gs://YOUR_BUCKET/spark-3.5-bigquery-0.44.1.jar,gs://YOUR_BUCKET/gcs-connector-hadoop3-latest.jar` + +_**IMAGE TO BE ADDED — Dataproc cluster creation screen showing the Properties section with\*\*\*\***** ****`spark.jars`**** ****key and the three JAR paths as the value. Tanwi to check with team for screenshot from a live Dataproc console. This is the most important screenshot on the page — the**** ****`spark.jars`**** ****\*\*\*\*Properties field is not obvious to find and a screenshot here prevents the most common setup error.**_ + +#### gcloud CLI + +```bash +gcloud dataproc clusters create zingg-cluster \ + --region=us-central1 \ + --image-version=2.2-debian12 \ + --master-machine-type=n2-standard-4 \ + --worker-machine-type=n2-standard-4 \ + --num-workers=2 \ + --optional-components=JUPYTER \ + --enable-component-gateway \ + --properties="^#^spark:spark.jars=\ +gs://$BUCKET/zingg-0.6.0.jar,\ +gs://$BUCKET/spark-3.5-bigquery-0.44.1.jar,\ +gs://$BUCKET/gcs-connector-hadoop3-latest.jar" +``` + +{% hint style="success" icon="right-long" %} +`numPartitions` should be set to approximately 20–30× your worker vCPU count. For a 2-worker `n2-standard-4` cluster (8 vCPUs each), start with 4–8. +{% endhint %} + +### Step 3: Open JupyterLab + +Once your cluster status shows **Running**, access the managed JupyterLab environment through the Component Gateway - no SSH or firewall configuration needed. + +1. Navigate to **Dataproc → Clusters**. +2. Click your cluster name. +3. Click the **Web Interfaces** tab. +4. Under **Component Gateway**, click the **JupyterLab** link. +5. Create a new notebook and select the **PySpark** kernel. + +_**IMAGE TO BE ADDED— Dataproc cluster Web Interfaces tab showing the Component Gateway section with the JupyterLab link highlighted. Tanwi to check with team for screenshot from a live Dataproc cluster.**_ + +### Step 4: Set a checkpoint directory and install Zingg + +Zingg uses a GCS checkpoint path as a persistent safety net for intermediate Spark computation state. Set this before running any phase. + +```python +checkpoint_path = f"gs://{BUCKET}/zingg_checkpoint" +spark.sparkContext.setCheckpointDir(checkpoint_path) +``` + +Install the Zingg Python package on the cluster: + +```python +checkpoint_path = f"gs://{BUCKET}/zingg_checkpoint" +spark.sparkContext.setCheckpointDir(checkpoint_path) +``` + +### Step 5: Set the model ID, storage paths, and import libraries + +`BUCKET` is your GCS bucket name. `modelId` is a unique name for this model run - Zingg uses it as the folder name under `zinggDir`. Use the same values across all steps in this workflow. + +```python +BUCKET = "your-bucket-name" +modelId = "testModelFebrl" +zinggDir = f"gs://{BUCKET}/models" + +MARKED_DIR = ( + f"gs://{BUCKET}/models/" + f"{modelId}/trainingData/marked/" +) +UNMARKED_DIR = ( + f"gs://{BUCKET}/models/" + f"{modelId}/trainingData/unmarked/" +) +``` + +`MARKED_DIR` and `UNMARKED_DIR` are derived automatically. Zingg writes labeled training pairs to `MARKED_DIR` during the `label` phase and reads them back during `train`. + +```python +import pandas as pd +import numpy as np +import os, time, uuid +from tabulate import tabulate +from ipywidgets import widgets, interact, GridspecLayout +import base64 +import pyspark.sql.functions as fn +from google.cloud import storage +from zingg.client import * +from zingg.pipes import * + +client = storage.Client() + +def cleanModel(): + """ + Clears previous training data to restart model learning from scratch. + """ + try: + bucket = client.get_bucket(BUCKET) + for prefix in [ + f"models/{modelId}/trainingData/marked/", + f"models/{modelId}/trainingData/unmarked/" + ]: + for blob in bucket.list_blobs(prefix=prefix): + blob.delete() + print("Model cleaned.") + except Exception as e: + print(f"Error: {str(e)}") + +def count_labeled_pairs(marked_pd): + """ + Returns positive, negative, and total labeled pair counts. + """ + if marked_pd.empty: + return 0, 0, 0 + n_total = len(np.unique(marked_pd['z_cluster'])) + n_positive = len(np.unique(marked_pd[marked_pd['z_isMatch'] == 1]['z_cluster'])) + n_negative = len(np.unique(marked_pd[marked_pd['z_isMatch'] == 0]['z_cluster'])) + return n_positive, n_negative, n_total +``` + +### Step 6: Build the arguments object + +`Arguments` is the central configuration object. Every phase in this workflow reads from the same `args` instance. + +```python +args = Arguments() +args.setModelId(modelId) +args.setZinggDir(zinggDir) +``` + +### Step 7: Preview your data + +Read your dataset from GCS and preview it before configuring the pipes. This is for your own verification; the actual Zingg input pipe is configured in Step 8. + +```python +csv_path = f"gs://{BUCKET}/test.csv" +spark_df = spark.read.csv(csv_path, header=True, inferSchema=True) +schema_list = [ + "id", "fname", "lname", "stNo", "add1", "add2", "city", "state", + "areacode", "dob", "ssn" +] +spark_df = spark_df.toDF(*schema_list) + +spark_df.limit(10).toPandas().head() +``` + +_**IMAGE TO BE ADDED — Jupyter notebook cell showing the preview output table with sample FEBRL data — the same customer appearing multiple times with field variations across rows. Source: not in the GCP docx (text-only guide). Tanwi to screenshot from a live notebook run. Same principle as the Databricks guide — this image shows readers the exact problem Zingg is solving before they configure anything. Place: below the\*\*\*\***** ****`spark_df.limit(10).toPandas().head()`**** ****\*\*\*\*line.**_ + +### Step 8: Configure input and output pipes + +`CsvPipe` connects Zingg to your GCS data. The schema string must match your dataset column names exactly. + +```python +schema = ( + "id string, fname string, " + "lname string, stNo string, " + "add1 string, add2 string, " + "city string, state string, " + "areacode string, dob string, " + "ssn string" +) + +inputPipe = CsvPipe("testFebrl", f"gs://{BUCKET}/test.csv", schema) +args.setData(inputPipe) + +output_path = f"gs://{BUCKET}/results" +outputPipe = CsvPipe("resultOutput", output_path) +args.setOutput(outputPipe) +``` + +{% hint style="success" icon="right-long" %} +**Read more**: Zingg supports CSV, Parquet, and JSON output on GCS. To push results directly to BigQuery after matching, use the BigQuery Spark connector. + +For connector config → [Connect BigQuery](../connect-your-data/connect-cloud-warehouses/connect-bigquery.md) +{% endhint %} + +### Step 9: Define fields and match types + +Every field in your input schema must appear in `fieldDefinition`. List the most important fields first as field order affects blocking quality. + +```python +id = FieldDefinition("id", "string", MatchType.EXACT) +fname = FieldDefinition("fname", "string", MatchType.FUZZY) +lname = FieldDefinition("lname", "string", MatchType.FUZZY) +stNo = FieldDefinition("stNo", "string", MatchType.FUZZY) +add1 = FieldDefinition("add1", "string", MatchType.FUZZY) +add2 = FieldDefinition("add2", "string", MatchType.FUZZY) +city = FieldDefinition("city", "string", MatchType.FUZZY) +state = FieldDefinition("state", "string", MatchType.FUZZY) +areacode = FieldDefinition("areacode", "string", MatchType.FUZZY) +dob = FieldDefinition("dob", "string", MatchType.EXACT) +ssn = FieldDefinition("ssn", "string", MatchType.EXACT) + +fieldDefs = [ + id, fname, lname, stNo, add1, add2, city, + state, areacode, dob, + ssn +] +args.setFieldDefinition(fieldDefs) +``` + +{% hint style="success" icon="right-long" %} +`FUZZY` handles variations like 'Jon' vs 'John' or 'St' vs 'Street'. `EXACT` requires a character-for-character match. `DONT_USE` excludes a field from matching but keeps it in output. + +For all match types → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +{% endhint %} + +### Step 10: Configure performance settings + +`numPartitions` controls how data is distributed across cluster nodes. `labelDataSampleSize` controls how much of the dataset is scanned when finding candidate pairs. + +For a 2-worker `n2-standard-4` cluster (8 vCPUs each), start with 4-8 + +```python +args.setNumPartitions(4) +``` + +Reduce to 0.1 if `findTrainingData` is slow on large datasets + +```python +args.setLabelDataSampleSize(0.5) +``` + +{% hint style="success" icon="right-long" %} +For 100k records use `labelDataSampleSize` between 0.1 and 0.5. For 1M+ records use 0.01 to 0.05. If `findTrainingData` takes too long, reduce by approximately 10× and try again. +{% endhint %} + +### Step 11: Find candidate pairs + +Zingg scans your dataset using the field rules defined in Step 9 and identifies pairs of records the model is uncertain about — edge cases where human input is most valuable. Candidate pairs are saved to `UNMARKED_DIR` in your GCS bucket. + +```python +options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +### Step 12: Load pairs for labeling + +```python +options = ClientOptions([ ClientOptions.PHASE, "label" ]) +zingg = ZinggWithSpark(args, options) +zingg.init() + +candidate_pairs_pd = getPandasDfFromDs(zingg.getUnmarkedRecords()) + +if candidate_pairs_pd.shape[0] == 0: + print("No pairs found. Run findTrainingData first.") +else: + z_clusters = list(np.unique(candidate_pairs_pd['z_cluster'])) + print(f"{len(z_clusters)} candidate pairs found for labeling") +``` + +### Step 13: Label pairs in the widget + +A widget displays each candidate pair side by side. For each pair select Match, No Match, or Uncertain. + +```python +ready_for_save = False +LABELS = {'Uncertain': 2, 'Match': 1, 'No Match': 0} + +n_pairs = int(candidate_pairs_pd.shape[0] / 2) +display_pd = candidate_pairs_pd.drop( + labels=['z_zid', 'z_prediction', + 'z_score', 'z_isMatch', 'z_zsource'], + axis=1) + +vContainers = [] +vContainers.append(widgets.HTML( + value=f'

Indicate if each of the ' + f'{n_pairs} record pairs is a match or not

')) + +for n in range(n_pairs): + candidate_left = display_pd.iloc[2*n].to_list() + candidate_right = display_pd.iloc[(2*n)+1].to_list() + html = '' + z_cluster = None + for i in range(display_pd.shape[1]): + col = display_pd.columns[i] + if col == 'z_cluster': + z_cluster = candidate_left[i] + html += '' + html += f'{col}' + html += f'{str(candidate_left[i])}' + html += f'{str(candidate_right[i])}' + html += '' + table = widgets.HTML( + value=f'{html}
') + label = widgets.ToggleButtons( + options=LABELS.keys(), button_style='info') + vContainers.append(widgets.VBox( + children=[table, label, widgets.HTML(value='
')])) + +display(widgets.VBox(children=vContainers)) +ready_for_save = True +``` + +_**IMAGE TO BE ADDED — Zingg labeling widget rendered in JupyterLab on Dataproc, showing two candidate records side by side with Match / No Match / Uncertain toggle buttons. Tanwi to check with team for screenshot from a live Dataproc notebook run.**_ + +{% hint style="success" icon="right-long" %} +Target 30–40 match pairs and 30–40 non-match pairs before training. Repeat Steps 11–14 in a loop until you reach this target. Label until all field types and data variation patterns in your schema are covered. If accuracy needs improvement after the first match run, return to labeling and focus on patterns that are underrepresented. +{% endhint %} + +### Step 14: Save labeled pairs + +```python +if not ready_for_save: + print("Run the widget cell first.") +else: + for pair in vContainers[1:]: + user_label = pair.children[1].get_interact_value() + start = pair.children[0].value.find('data-title="') + if start > 0: + start += len('data-title="') + end = pair.children[0].value.find('"', start+2) + pair_id = pair.children[0].value[start:end] + candidate_pairs_pd.loc[ + candidate_pairs_pd['z_cluster'] == pair_id, + 'z_isMatch'] = LABELS.get(user_label) + + zingg.writeLabelledOutputFromPandas( + candidate_pairs_pd, args) + + marked_pd = getPandasDfFromDs( + zingg.getMarkedRecords()) + n_pos, n_neg, n_tot = \ + count_labeled_pairs(marked_pd) + print(f"Total pairs labeled: {n_tot}") + print(f"Positive matches: {n_pos}") + print(f"Non-matches: {n_neg}") + print("Run Steps 11-14 again if you need more pairs.") + ready_for_save = False +``` + +### Step 15: Generate model documentation (optional) + +Run `generateDocs` after labeling to produce readable HTML reports of your training data, both matched and non-matched pairs. Use this to verify label consistency and share a visual audit with subject matter experts before training. + +```python +options = ClientOptions([ ClientOptions.PHASE, "generateDocs" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() + +DOCS_DIR = f"{zinggDir}/{modelId}/docs/" +from IPython.core.display import display, HTML + +print("--- Model Summary ---") +with open(DOCS_DIR + "model.html", 'r') as f: + display(HTML(f.read())) + +print("--- Data Distribution ---") +with open(DOCS_DIR + "data.html", 'r') as f: + display(HTML(f.read())) +``` + +_**IMAGE TO BE ADDED—**** ****`generateDocs`****\*\*\*\* \*\*\*\*HTML output rendered inside JupyterLab on Dataproc showing labeled pair examples. Source: not in the GCP docx. Tanwi to check with team for screenshot from a live notebook run.**_ + +{% hint style="success" icon="right-long" %} +`generateDocs` is optional. Skip it if you have 30–40 matches and 30–40 non-matches and are confident in your labeling quality. +{% endhint %} + +### Step 16: Train and match + +`trainMatch` combines the `train` and `match` phases into a single call. Zingg builds a model from your labeled pairs and immediately applies it to the full dataset. This is the most compute-intensive step — it distributes the workload across all Dataproc nodes. + +```python +options = ClientOptions([ ClientOptions.PHASE, "trainMatch" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +You can also run `train` and `match` as two separate phases if you want to inspect the trained model before running the full dataset match: + +Train separately: + +```python +options = ClientOptions([ ClientOptions.PHASE, "train" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +Then match separately: + +```python +options = ClientOptions([ ClientOptions.PHASE, "match" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +### Step 17: View output + +Match output is written to `output_path` in your GCS bucket as distributed part-files. Spark reads them back and merges them automatically. + +```python +outputDF = spark.read.csv(output_path, header=False, inferSchema=True) + +colNames = [ + "z_score", "z_cluster", "z_zid", "id", "fname", "lname", "stNo", "add1", + "add2", "city", "state", "areacode", "dob", "ssn" +] + +final_results = outputDF.toDF(*colNames) +final_results.show(10) +``` + +_**IMAGE TO BE ADDED — match output table in JupyterLab on Dataproc showing resolved records with\*\*\*\***** ****`z_cluster`**** ****column visible. Highlight two rows sharing the same**** ****`z_cluster`**** ****\*\*\*\*value to show they have been resolved to the same entity.Tanwi to check with team for screenshot from a live notebook run.**_ + +{% hint style="success" icon="right-long" %} +* `z_cluster`— unique entity ID assigned by Zingg. All records sharing the same `z_cluster` represent the same real-world entity. Group by `z_cluster` to collapse duplicates into a golden record. +* `z_score` — model confidence. Values closer to 1.0 indicate a stronger match. +* `z_zid` — unique internal row identifier assigned during this run. + +For threshold guidance and full output column definitions → [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) +{% endhint %} +{% endtab %} + +{% tab title="Enterprise" %} + +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Read more**: + +* Tune accuracy → [Improve Accuracy](../tuning/improve-accuracy/) +* Understand scores and set thresholds → [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) +* Push results to BigQuery → [Connect BigQuery](../connect-your-data/connect-cloud-warehouses/connect-bigquery.md) +{% endhint %} diff --git a/docs/platform-guides/platform-guide-for-microsoft-fabric.md b/docs/platform-guides/platform-guide-for-microsoft-fabric.md new file mode 100644 index 000000000..f2d6a93cf --- /dev/null +++ b/docs/platform-guides/platform-guide-for-microsoft-fabric.md @@ -0,0 +1,481 @@ +--- +description: >- + End-to-end guide to running Zingg on Microsoft Fabric from workspace setup and + Lakehouse creation to your first match result. Covers Community (open source) + and Enterprise. +--- + +# Platform Guide for Microsoft Fabric + +Microsoft Fabric paired with Zingg gives you entity resolution with built-in data governance via Microsoft Purview. Fabric's OneLake provides a single storage layer for all your data. Zingg runs on Fabric Spark pools using the same Python API as on other platforms. + +{% hint style="success" icon="right-long" %} +* Tested with Fabric Runtime 1.3 (Spark 3.5). +* Download the sample Fabric notebook from `github.com/zinggAI/zingg/blob/main/examples/fabric/ExampleNotebook.ipynb` and upload it to your workspace to follow this guide. +{% endhint %} + +{% tabs %} +{% tab title="Community" %} +Uses `Arguments`, `FieldDefinition`, `CsvPipe`, and `ZinggWithSpark`. The workflow runs across four notebooks. + +### Fabric workspace setup + +Complete these four steps in the Fabric UI before opening any notebook. + +#### **Step 1: Create a Fabric workspace** + +If you are new to Fabric, sign up for a free trial at `microsoft.com/fabric`. + +1. Go to **Workspaces** and click **New workspace**. +2. Name it something like `Zingg-Fabric`. +3. When prompted for a session cluster, choose **New Standard Session**. + +#### **Step 2: Create a Zingg Environment** + +Fabric Environments let you install JARs that persist across notebook sessions. Zingg requires its JAR to be installed in an Environment before any notebook runs. + +1. Inside your workspace, go to the **Environment** tab and click **New Environment**. +2. Name it `Zingg Environment`. + +#### **Step 3: Install the Zingg JAR in the Environment** + +The Zingg JAR must be installed as a Custom Library in your Environment so Fabric's Spark runtime can find it. + +1. Go to `github.com/zinggAI/zingg/releases` and download the latest release `tar` file. +2. Extract the `tar` file and locate the JAR file inside it. +3. Open your `Zingg Environment`, go to **Custom Library**, and upload the JAR file. +4. Click **Save** and then **Publish** the Environment. + +#### **Step 4: Create a Lakehouse and upload your data** + +Zingg reads from and writes to OneLake. Create a Lakehouse to give Zingg a storage location for your data, model files, and output. + +1. Inside your workspace, click **New Item** → **Lakehouse**. +2. Give the Lakehouse a name (for example `ZinggLakehouse`). +3. Go inside the Lakehouse, click **Get Data**, and upload your CSV file. + +{% hint style="success" icon="right-long" %} +Sample data for testing: `github.com/zinggAI/zingg/blob/main/examples/febrl/test.csv` + +Zingg also supports Delta Lake tables, Parquet, and JSON - change the format in the input pipe configuration. + +For all connector formats → [Connect Microsoft Fabric](../connect-your-data/connect-cloud-warehouses/connect-microsoft-fabric.md) +{% endhint %} + +### Notebook 01: Set up Zingg + +Create a new notebook in your workspace, attach it to the `Zingg Environment`, and select **PySpark** as the kernel. All four notebooks in this workflow call `%run 01-setting_up_zingg` at the top — so everything configured here is inherited by Notebooks 02, 03, and 04 automatically. + +#### **Step 5: Verify Spark is configured correctly** + +Confirm Fabric's Spark runtime is running before proceeding. + +```python +spark.sparkContext.getConf().get('spark.hadoop.trident.workspace.id') +``` + +#### **Step 6: Set the checkpoint directory and install Zingg** + +Zingg uses a checkpoint directory to store intermediate Spark computation state. Set this to a path inside your OneLake `Files` directory. + +```python +spark.sparkContext.setCheckpointDir("Files") +``` + +Install the Zingg Python package: + +```python +pip install zingg +``` + +Verify the installation: + +```python +!pip show zingg +``` + +#### **Step 7: Set the model ID and storage paths** + +`zinggDir` is the root path for all Zingg model files, training data, and output. `modelId` is the unique name for this model run. Use the same values across all four notebooks. + +Replace `` and `` with the actual IDs from your Fabric workspace and Lakehouse. You can find them in the browser address bar when viewing your Lakehouse. + +```python +zinggDir = ( + "abfss://@onelake" + ".dfs.fabric.microsoft.com/" + "/Files/models" +) +modelId = "oss_model" +``` + +`MARKED_DIR` and `UNMARKED_DIR` are derived automatically. Zingg writes labeled training pairs to `MARKED_DIR` during the `label` phase and reads them back during `train`. + +```python +MARKED_DIR = zinggDir + "/" + modelId + "/trainingData/marked/" +UNMARKED_DIR = zinggDir + "/" + modelId + "/trainingData/unmarked/" +``` + +#### Step 8: Import libraries and set up helper functions + +```python +import pandas as pd +import numpy as np +from ipywidgets import widgets, interact, GridspecLayout +import base64 +import pyspark.sql.functions as fn + +from zingg.client import * +from zingg.pipes import * + +def count_labeled_pairs(marked_pd): + """ + Returns positive, negative, uncertain, + and total labeled pair counts. + """ + n_total = len(np.unique(marked_pd['z_cluster'])) + n_positive = len(np.unique(marked_pd[marked_pd['z_isMatch'] == 1]['z_cluster'])) + n_negative = len(np.unique(marked_pd[marked_pd['z_isMatch'] == 0]['z_cluster'])) + n_uncertain = len(np.unique(marked_pd[marked_pd['z_isMatch'] == 2]['z_cluster'])) + return n_positive, n_negative, n_uncertain, n_total + +def fix_void_columns(df): + """ + Handles columns where all values are None + to prevent save errors. + """ + for col in df.columns: + if df[col].apply(lambda x: x is None).all(): + df[col] = df[col].astype(str) + return df +``` + +#### **Step 9: Build the arguments object** + +`Arguments` is the central configuration object. Every phase in this workflow reads from the same `args` instance. + +```python +args = Arguments() +args.setModelId(modelId) +args.setZinggDir(zinggDir) +``` + +#### **Step 10: Configure performance settings** + +`numPartitions` controls how data is distributed across Spark workers. Disabling Adaptive Query Execution gives Zingg more predictable Spark behavior. + +```python +args.setNumPartitions(32) +spark.conf.set("spark.sql.adaptive.enabled", False) +``` + +{% hint style="danger" icon="right-long" %} +Set `numPartitions` to approximately 20–30× your worker vCPU count. `labelDataSampleSize` is set in Notebook 02 where the labeling loop runs — see Step 13. +{% endhint %} + +#### **Step 11: Preview your data** + +Read your CSV from OneLake and preview it before configuring the Zingg pipes. This is for your own verification—the Zingg input pipe is configured in Step 12. + +```python +import pandas as pd + +schema = ["rec_id", "fname", "lname", "stNo", "add1", "add2", "city", "areacode", "state", "dob", "ssn"] + +data = pd.read_csv( + "abfss://@onelake" + ".dfs.fabric.microsoft.com/" + "/Files/test.csv", + header=None +) +data.columns = schema +data.head() +``` + +_**IMAGE TO BE ADDED — Fabric notebook cell showing the data preview output table with sample FEBRL records — the same entity appearing multiple times with field variations across rows. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ + +#### **Step 12: Configure input and output pipes** + +`CsvPipe` connects Zingg to your OneLake data. The schema string must match your dataset column names exactly. + +```python +schema = ( + "rec_id string, fname string, " + "lname string, stNo string, " + "add1 string, add2 string, " + "city string, areacode string, " + "state string, dob string, " + "ssn string" +) + +inputPipe = CsvPipe( + "inputpipe", + "abfss://@onelake" + ".dfs.fabric.microsoft.com/" + "/Files/test.csv", + schema +) +args.setData(inputPipe) + +output_path = ( + "abfss://@onelake" + ".dfs.fabric.microsoft.com/" + "/Files/Output" + + modelId +) +outputPipe = CsvPipe("resultOutput", output_path) +args.setOutput(outputPipe) +``` + +{% hint style="success" icon="right-long" %} +**Read more**: To use Delta tables instead of CSV, change the format to `delta` and point to your Lakehouse Tables directory. For all connector formats → [Connect Microsoft Fabric](../connect-your-data/connect-cloud-warehouses/connect-microsoft-fabric.md) +{% endhint %} + +#### **Step 13: Define fields and match types** + +Every field in your input schema must appear in `fieldDefinition`. List the most important fields first as field order affects blocking quality. + +```python +rec_id = FieldDefinition("rec_id", "string", MatchType.DONT_USE) +fname = FieldDefinition("fname", "string", MatchType.FUZZY) +lname = FieldDefinition("lname", "string", MatchType.FUZZY) +stNo = FieldDefinition("stNo", "string", MatchType.FUZZY) +add1 = FieldDefinition("add1", "string", MatchType.FUZZY) +add2 = FieldDefinition("add2", "string", MatchType.FUZZY) +city = FieldDefinition("city", "string", MatchType.FUZZY) +areacode = FieldDefinition("areacode", "string", MatchType.FUZZY) +state = FieldDefinition("state", "string", MatchType.FUZZY) +dob = FieldDefinition("dob", "string", MatchType.EXACT) +ssn = FieldDefinition("ssn", "string", MatchType.EXACT) + +fieldDefs = [ + rec_id, fname, lname, stNo, add1, + add2, city, areacode, state, dob, + ssn +] +args.setFieldDefinition(fieldDefs) +``` + +{% hint style="success" icon="right-long" %} +`FUZZY` handles variations like 'Jon' vs 'John' or 'St' vs 'Street'. `EXACT` requires a character-for-character match. `DONT_USE` excludes a field from matching but keeps it in output — use this for record identifiers. For all match types → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +{% endhint %} + +### Notebook 02: Find training data and label pairs + +This notebook runs `findTrainingData` and `label`. It calls `%run 01-setting_up_zingg` at the top so all configuration from Notebook 01 is inherited automatically. + +#### **Step 14: Set `labelDataSampleSize`** + +`labelDataSampleSize` controls how much of your dataset is scanned when finding candidate pairs. + +```python +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.4) +``` + +{% hint style="success" icon="right-long" %} +For 100k records use `labelDataSampleSize` between 0.1 and 0.5. For 1M+ records use 0.01 to 0.05. If `findTrainingData` is slow, reduce by approximately 10× and try again. +{% endhint %} + +#### **Step 15: Find candidate pairs** + +Zingg scans your dataset using the field rules defined in Step 13 and selects the most informative pairs for labeling — edge cases where the model has the most to learn. Candidate pairs are saved to `UNMARKED_DIR` in your OneLake Lakehouse. + +```python +options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +#### Step 16: Load pairs for labeling + +```python +options = ClientOptions([ ClientOptions.PHASE, "label" ]) +zingg = ZinggWithSpark(args, options) +zingg.init() + +candidate_pairs_pd = getPandasDfFromDs(zingg.getUnmarkedRecords()) + +if candidate_pairs_pd.shape[0] == 0: + print("No pairs found. Run findTrainingData first.") +else: + z_clusters = list(np.unique(candidate_pairs_pd['z_cluster'])) + print(f"{len(z_clusters)} candidate pairs found for labeling") +``` + +#### **Step 17: Label pairs in the widget** + +A widget displays each candidate pair side by side. For each pair, select Match, No Match, or Uncertain. + +```python +ready_for_save = False +LABELS = {'Uncertain': 2, 'Match': 1, 'No Match': 0} + +n_pairs = int(candidate_pairs_pd.shape[0] / 2) +display_pd = candidate_pairs_pd.drop( + labels=['z_zid', 'z_prediction', + 'z_score', 'z_isMatch', 'z_zsource'], + axis=1) + +vContainers = [] +vContainers.append(widgets.HTML( + value=f'

Indicate if each of the ' + f'{n_pairs} record pairs is a match or not

')) + +for n in range(n_pairs): + candidate_left = display_pd.loc[2*n].to_list() + candidate_right = display_pd.loc[(2*n)+1].to_list() + html = '' + z_cluster = None + for i in range(display_pd.shape[1]): + column_name = display_pd.columns[i] + if column_name == 'z_cluster': + z_cluster = candidate_left[i] + html += '' + html += f'{column_name}' + html += f'{str(candidate_left[i])}' + html += f'{str(candidate_right[i])}' + html += '' + table = widgets.HTML( + value=f'{html}
') + label = widgets.ToggleButtons( + options=LABELS.keys(), button_style='info') + vContainers.append(widgets.VBox( + children=[table, label, + widgets.HTML(value='
')])) + +display(widgets.VBox(children=vContainers)) +ready_for_save = True +``` + +_**IMAGE TO BE ADDED — Zingg labeling widget running inside a Fabric notebook showing two candidate records side by side with Match / No Match / Uncertain toggle buttons. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ + +{% hint style="success" icon="right-long" %} +Target 30–40 match pairs and 30–40 non-match pairs before training. Repeat Steps 15–18 in a loop until you reach this target. Label until all field types and data variation patterns in your schema are covered. If accuracy needs improvement after the first match run, return to labeling and focus on patterns that are missing or underrepresented. +{% endhint %} + +#### **Step 18: Save labeled pairs** + +After labeling pairs in the widget, run this cell to save your labels to the training data folder in OneLake. + +```python +if not ready_for_save: + print("Run the widget cell first.") +else: + for pair in vContainers[1:]: + user_assigned_label = pair.children[1].get_interact_value() + start = pair.children[0].value.find('data-title="') + if start > 0: + start += len('data-title="') + end = pair.children[0].value.find('"', start+2) + pair_id = pair.children[0].value[start:end] + candidate_pairs_pd.loc[ + candidate_pairs_pd['z_cluster'] == pair_id, + 'z_isMatch'] = LABELS.get(user_assigned_label) + + notebookutils.fs.mkdirs(MARKED_DIR) + zingg.writeLabelledOutputFromPandas( + candidate_pairs_pd, args) + + marked_pd_df = getPandasDfFromDs( + zingg.getMarkedRecords()) + n_pos, n_neg, n_uncer, n_tot = \ + count_labeled_pairs(marked_pd_df) + print(f"Out of total {n_tot} pairs,") + print(f"You have accumulated {n_pos} pairs labeled as positive matches.") + print(f"You have accumulated {n_neg} pairs labeled as not matches.") + print(f"You have accumulated {n_uncer} pairs labeled as uncertain.") + print("Run Steps 15-18 again if you need more pairs.") + ready_for_save = False +``` + +{% hint style="success" icon="right-long" %} +`notebookutils.fs.mkdirs(MARKED_DIR)` creates the target directory in OneLake if it does not already exist. This is a Fabric-specific utility — it is not used on other platforms. +{% endhint %} + +### Notebook 03: Generate model documentation (optional) + +This notebook runs `generateDocs`. It calls `%run 01-setting_up_zingg` at the top. Run it after labeling and before training to inspect label quality. Skip it if you are confident in your labeling and want to proceed directly to Notebook 04. + +#### **Step 19: Generate and view model documentation** + +Run `generateDocs` to produce readable HTML reports of your labeled training data — both matched and non-matched pairs. Use this to verify label consistency and share with subject matter experts before committing to training. + +```python +options = ClientOptions([ ClientOptions.PHASE, "generateDocs" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() + +DOCS_DIR = zinggDir + "/" + modelId + "/docs/" + +displayHTML(open(DOCS_DIR + "model.html", 'r').read()) +displayHTML(open(DOCS_DIR + "data.html", 'r').read()) +``` + +_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\* \*\*\*\*HTML output rendered inside a Fabric notebook showing labeled pair examples in a table. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ + +### Notebook 04: Train and match + +This notebook runs `trainMatch` and displays the output. It calls `%run 01-setting_up_zingg` at the top. + +#### **Step 20: Train and match** + +`trainMatch` combines `train` and `match` into a single phase. Zingg builds a model from your labeled pairs and immediately applies it to the full dataset. This is the most compute-intensive step; Spark distributes the workload across all Fabric Spark nodes. + +```python +options = ClientOptions([ ClientOptions.PHASE, "trainMatch" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +You can also run `train` and `match` as separate phases if you want to inspect the trained model before running the full dataset: + +```python +options = ClientOptions([ ClientOptions.PHASE, "train" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() + +options = ClientOptions([ ClientOptions.PHASE, "match" ]) +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +#### Step 21: View output + +Match output is written to `output_path` in your OneLake Lakehouse. Read it back into a Spark DataFrame to inspect the resolved clusters. + +```python +colNames = [ + "z_minScore", "z_maxScore", "z_cluster", "rec_id", "fname", "lname", + "stNo", "add1", "add2", "city", "areacode", "state", "dob", "ssn" +] + +outputDF = spark.read.csv(output_path) +outputDF = outputDF.toDF(*colNames) +display(outputDF) +print(outputDF.count()) +``` + +_**IMAGE TO BE ADDED— Match output table in a Fabric notebook showing resolved records with\*\*\*\***** ****`z_cluster`**** ****\*\*\*\*column visible — two rows sharing the same cluster value highlighted to illustrate entity resolution. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ +{% endtab %} + +{% tab title="Enterprise" %} + +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Read more**: + +* Tune accuracy → [Improve Accuracy](../tuning/improve-accuracy/) +* Understand scores and set thresholds → [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) +{% endhint %} + +{% hint style="success" icon="right-long" %} +Download the notebooks used in this guide: + +* Community notebooks (NB01–04): `github.com/zinggAI/zingg/tree/main/examples/fabric` +* Enterprise notebooks (NB01–07): included in your Zingg Enterprise package +{% endhint %} diff --git a/docs/platform-guides/platform-guide-for-snowflake.md b/docs/platform-guides/platform-guide-for-snowflake.md new file mode 100644 index 000000000..8197dc3a7 --- /dev/null +++ b/docs/platform-guides/platform-guide-for-snowflake.md @@ -0,0 +1,350 @@ +--- +description: >- + End-to-end guide to running Zingg on Snowflake using Snowflake as your data + source on AWS EC2 with Docker (Community) or running Zingg natively inside + Snowflake with Enterprise. +tags: + - ent +--- + +# Platform Guide for Snowflake + +{% hint style="success" icon="right-long" %} +Run Zingg with Snowflake as your data source using the Snowflake Spark connector on EC2, or run Zingg natively inside Snowflake with Enterprise. +{% endhint %} + +{% tabs %} +{% tab title="Community" %} +{% hint style="success" icon="right-long" %} +For the Snowflake connector JSON config → [Connect Snowflake](../connect-your-data/connect-cloud-warehouses/connect-snowflake.md) Sample Snowflake config file: `github.com/zinggAI/zingg/blob/main/examples/febrl/configSnow.json` +{% endhint %} + +This guide requires an AWS account with EC2 and Lambda access, and a Snowflake account. Ensure your EC2 security group allows outbound HTTPS (port 443) for Snowflake connectivity. A `t3.medium` or larger EC2 instance is sufficient for labeling and training phases. For large datasets (1M+ records) use `m5.xlarge` or above. + +### **Step 1: Set up Zingg on AWS EC2** + +Create or use an existing EC2 instance. Connect to it using VS Code Remote SSH: + +1. Install the VS Code Remote SSH extension. +2. Add your EC2 host to the SSH config file: + +```bash +Host + HostName + User ec2-user + IdentityFile + PreferredAuthentications publickey +``` + +3. Click **Open a Remote Window** in VS Code, select your EC2 host, and connect. 4. Pull the Zingg Docker image and start a bash session inside the container: + +```bash +docker pull zingg/zingg:0.6.0 +docker run -it zingg/zingg:0.6.0 bash +``` + +Inside the container, your container ID is the alphanumeric string between `@` and `:` in the terminal prompt. For example in `root@fab997383957:/zingg#`, the container ID is `fab997383957`. Note this value—you will need it in Step 3. + +Before connecting to Snowflake, download the Snowflake Spark connector JAR and the Snowflake JDBC driver and add them to `zingg.conf` inside the container: + +```bash +spark.jars=snowflake-jdbc-3.13.19.jar,spark-snowflake_2.12-2.10.0-spark_3.1.jar +``` + +{% hint style="success" icon="right-long" %} +**Read more**: For JAR download links and full `zingg.conf` setup → [Connect Snowflake](../connect-your-data/connect-cloud-warehouses/connect-snowflake.md) +{% endhint %} + +### **Step 2: Connect Zingg to Snowflake** + +Configure your `config.json` with your Snowflake connection details, field definitions, and performance settings. Use `examples/febrl/configSnow.json` as a starting point. + +```json +{ + "data" : [ { + "name" : "identityResolution", + "format" : "net.snowflake.spark.snowflake", + "props" : { + "sfUrl" : "your-account.snowflakecomputing.com", + "sfUser" : "your-username", + "sfPassword" : "your-password", + "sfDatabase" : "your-database", + "sfSchema" : "MYSCHEMA", + "sfWarehouse" : "COMPUTE_WH", + "dbtable" : "your-customer-table", + "application" : "zingg_zingg" + } + } ], + "output" : [ { + "name" : "unifiedCustomers", + "format" : "net.snowflake.spark.snowflake", + "props" : { + "sfUrl" : "your-account.snowflakecomputing.com", + "sfUser" : "your-username", + "sfPassword" : "your-password", + "sfDatabase" : "your-database", + "sfSchema" : "MYSCHEMA", + "sfWarehouse" : "COMPUTE_WH", + "dbtable" : "your-output-table", + "application" : "zingg_zingg" + } + } ], + "modelId" : "100", + "zinggDir" : "models", + "numPartitions" : 4, + "labelDataSampleSize" : 0.5, + "fieldDefinition" : [ + { + "fieldName" : "fname", + "matchType" : "FUZZY", + "fields" : "fname", + "dataType" : "string" + }, + { + "fieldName" : "lname", + "matchType" : "FUZZY", + "fields" : "lname", + "dataType" : "string" + } + ] +} +``` + +{% hint style="success" icon="right-long" %} +Replace `your-customer-table` with your source Snowflake table name and `your-output-table` with the table Zingg will create for resolved output. For all `fieldDefinition` parameters → [Configuration Schema](../reference/configuration-schema.md) + +Set `numPartitions` to approximately 20–30× your Snowflake warehouse vCPU count. For a standard XS warehouse (1 node), start with 4. For a LARGE warehouse (4 nodes), use 16–32. Reduce `labelDataSampleSize` to 0.05–0.1 for tables with more than 1M rows. +{% endhint %} + +### **Step 3: Create an AWS Lambda function to trigger Zingg phases** + +The Lambda function receives a Zingg phase name, SSHes into EC2, and executes that phase inside the Docker container. `findTrainingData`, `train`, and `match` run via Lambda. The `label` phase runs interactively on EC2 directly. + +Create a file called `lambda_function.py` with the following code. Replace all placeholder values before deploying: + +```python +import json +import paramiko + +def lambda_handler(event, context): + status_code = 200 + try: + event_body = event["body"] + payload = json.loads(event_body) + row = payload["data"] + row_number = row[0][0] + phase = row[0][1] + + if phase not in ['findTrainingData', 'match', 'train', 'checklog']: + raise ValueError + + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + '', + username='ec2-user', + key_filename=('/var/task/your-key.pem') + ) + + if phase != 'checklog': + command = ( + "docker exec " + " " + "bash -c 'zingg.sh " + "--properties-file " + "config/zingg.conf " + "--phase " + phase + " --conf examples/febrl/" + "configSnow.json'" + " > logfile.txt" + ) + stdin, stdout, stderr = ssh.exec_command(command) + result = json.dumps({"data" : [[0, "Started phase: " + phase]]}) + else: + command = "cat logfile.txt" + stdin, stdout, stderr = ssh.exec_command(command) + output = stdout.readlines() + result = json.dumps({"data" : [[0, output]]}) + + return {"statusCode" : status_code, "body" : result} + + except ValueError: + return {"statusCode" : 400, "body" : json.dumps({"data" : [[0, "Invalid parameters"]]})} + except Exception as e: + return {"statusCode" : 400, "body" : json.dumps({"data" : [[0, str(e)]]})} +``` + +{% hint style="success" icon="right-long" %} +The `checklog` option lets you read the Zingg log file directly from Snowflake to monitor phase progress without logging back into EC2. +{% endhint %} + +### **Step 4: Deploy the Lambda function** + +1. Inside EC2, create a virtual environment and install dependencies: + +```bash +python3 -m venv awspackagelayer +source awspackagelayer/bin/activate +pip install paramiko + +#Zip the site - packages +zip -r deploy.zip awspackagelayer/lib/python3.7/site-packages + +#Add the script and key file +zip -g deploy.zip lambda_function.py +zip -g deploy.zip your-key.pem +``` + +{% hint style="success" icon="right-long" %} +When the `.pem` key file is included in the zip, Lambda extracts it to `/var/task/your-key.pem` at runtime. The `key_filename` value in `lambda_function.py` must be set to `/var/task/your-key.pem` before zipping and deploying. If the path is wrong, the SSH connection will fail. +{% endhint %} + +2. Go to **AWS Lambda** → **Create Function**. Give it a name and select Python. Create a new role with basic Lambda permissions. +3. Upload the `deploy.zip` via **Upload From** → `.zip` file. +4. Test the function with this Event JSON: + +```json +{ "body" : "{\"data\": [[0, \"findTrainingData\"]]}" } +``` + +{% hint style="success" icon="right-long" %} +A successful test returns `HTTP 200` and `Started phase: findTrainingData`. Monitor progress via `tail -f logfile.txt` on EC2. +{% endhint %} + +_**IMAGE TO BE ADDED — AWS Lambda function test screen showing a successful HTTP 200 response with the "Started phase: findTrainingData" body. Tanwi to check with team for screenshot this from a live Lambda test run and add here. Caption: "Lambda function test returning HTTP 200 — Zingg phase started successfully."**_ + +### **Step 5: Connect Lambda to Snowflake via external function** + +1. Create a new IAM role. Entity type: **Another AWS account**. Specify your AWS Account ID. +2. Create an AWS API Gateway: REST API with regional endpoint. Create a resource with a POST method. Integration type: Lambda function with Lambda proxy integration. +3. Deploy the API and note the invoke URL. +4. In Snowflake, get the API integration ARN to update the IAM trust relationship: + +```sql +DESC INTEGRATION your_api_integration; +``` + +Take the `API_AWS_ROLE_ARN` value from the output and add it as a trusted entity in the IAM role trust policy. This authorises Snowflake's AWS account to assume the role and invoke the Lambda function. + +5. Create the external function in Snowflake: + +```sql +CREATE OR REPLACE EXTERNAL FUNCTION run_zingg(phase VARCHAR) RETURNS VARIANT + API_INTEGRATION = AS ''; +``` + +### Step 6: Run Zingg phases from Snowflake + +**Run findTrainingData** + +```sql +SELECT run_zingg('findTrainingData'); +``` + +**Run label interactively on EC2 directly** + +```sql +./zingg.sh --phase label --conf examples/febrl/configSnow.json +``` + +**Run train** + +```sql +SELECT run_zingg('train'); +``` + +**Run match** + +```sql +SELECT run_zingg('match'); +``` + +**Check logs from Snowflake** + +```sql +SELECT run_zingg('checklog'); +``` + +{% hint style="success" icon="right-long" %} +Run `findTrainingData` to generate candidate pairs. Zingg selects the most informative pairs from your data, not random samples. Run `label` interactively on EC2 to label those pairs as Match, No Match, or Uncertain. Label until all field types and data variation patterns in your schema are represented. If accuracy needs improvement after the first match run, return to labeling and focus on patterns that are missing or underrepresented. +{% endhint %} + +### **Running phases asynchronously** + +For large tables, Zingg phases can run for several hours. The SSH connection will time out and kill the job if you run synchronously. Use `nohup` to run phases as background processes on EC2: + +```bash +nohup ./scripts/zingg.sh --properties-file ~/zingg/snowEnv.txt --phase findTrainingData --conf ~/zingg/snowConfigFile.json & +``` + +Monitor progress from EC2: + +```bash +tail -f nohup.out +``` + +Or from Snowflake: + +```bash +SELECT run_zingg('checklog'); +``` + +{% hint style="success" icon="right-long" %} +Output is written to the Snowflake table configured in the `output` section of your `config.json`. Records with the same `Z_CLUSTER` value represent the same real-world entity. For output column definitions → [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) +{% endhint %} + +### **Snowflake Iceberg Tables variant** + +This variant uses Azure Blob Storage as the Iceberg storage layer with Snowflake managing the Iceberg tables and Zingg running locally via Docker. + +#### **Prerequisites:** + +* Active Azure Blob Storage account with a container +* Snowflake account with a warehouse and database + +#### **Step 1: Create the Iceberg setup in Snowflake** + +1. Create an Azure Blob Storage account and container. Note the storage account name and container name. +2. In Snowflake: create a warehouse, database, and external volume pointing to Azure Blob Storage. +3. Run `DESC VOLUME ` and click the `AZURE_CONSENT_URL` to authorise Snowflake access to Azure storage. +4. Note the `AZURE_MULTI_TENANT_APP_NAME` value (before the underscore) and add it as a role assignment in your Azure storage account. +5. Verify: `SYSTEM$VERIFY_EXTERNAL_VOLUME('your_volume_name')`. +6. Create your Iceberg table and load your data. + +#### **Step 2: Configure Zingg** + +Zingg accesses Snowflake Iceberg tables through the same Snowflake Spark connector as standard Snowflake tables. The Iceberg layer is transparent to Zingg — reference the Iceberg table by name in `dbtable` exactly as you would a regular Snowflake table. Ensure Snowflake has been authorised to access your Azure Blob Storage external volume before running any Zingg phase. + +```json +{ + "data" : [ { + "name" : "icebergInput", + "format" : "net.snowflake.spark.snowflake", + "props" : { + "sfUrl" : "your-account.snowflakecomputing.com", + "sfUser" : "your-username", + "sfPassword" : "your-password", + "sfDatabase" : "your-database", + "sfSchema" : "MYSCHEMA", + "sfWarehouse" : "COMPUTE_WH", + "dbtable" : "your-iceberg-table-name", + "application" : "zingg_zingg" + } + } ] +} +``` + +#### **Step 3: Run Zingg phases** + +Same as the main guide above; pull the Zingg Docker image, configure Lambda, and invoke phases from Snowflake. +{% endtab %} + +{% tab title="Enterprise" %} +{% hint style="info" icon="right-long" %} +Enterprise only. Zingg Enterprise runs natively inside Snowflake using Snowpark. No EC2, no Docker, and no Lambda required. + +Enterprise requires a Zingg licence and the Enterprise Snowflake package. [Contact Zingg to get access](https://www.zingg.ai/company/contact/contact). +{% endhint %} + +**Content for the Enterprise Snowflake platform guide is being prepared. This section will be updated with full step-by-step instructions once confirmed by the team.** +{% endtab %} +{% endtabs %} diff --git a/docs/recipes-and-integration/combine-match-models.md b/docs/recipes-and-integration/combine-match-models.md new file mode 100644 index 000000000..e1d1b150b --- /dev/null +++ b/docs/recipes-and-integration/combine-match-models.md @@ -0,0 +1,133 @@ +--- +description: >- + Merge match results from separate Zingg jobs into a single unified identity + graph. +tags: + - ent +--- + +# Combine Match Models + +In some architectures, you run multiple separate Zingg match jobs on different datasets, different time windows, or with different configurations, and then need to merge the results into a single unified identity graph. Combining match models is a **configuration-based approach**, not a separate phase. You define a graph config JSON with vertices (your data sources and match outputs) and edges (how they connect). + +In the resulting graph, Zingg can relate different match models together. For example: a source system that only contains user IDs and email, another with user names and phone numbers, and a third with full person addresses, each with its own Zingg match model. Combining them produces one unified identity graph that spans all three models. + +This process is different from comparing model results (`diff`), which evaluates two models against each other for quality assessment before deployment.. + +### When to use Combine match models + +
+ +Matching across multiple time-partitioned datasets + +You have data partitioned by month or quarter. You run a separate Zingg job on each partition and then combine the results into a single identity graph covering all time\ +periods. + +
+ +
+ +Combining results from different source systems + +You run one Zingg job on CRM records and a separate job on transaction data. Each job produces its own `Z_CLUSTER` output. Combine match models merge these into a single unified cluster space. + +
+ +
+ +Ensemble matching + +You run multiple Zingg jobs with different configurations; for example, one optimized for high precision and one for high recall, and combine the results to get the benefit of both approaches. + +
+ +### How it works + +The combination is defined in a JSON config file with two top-level arrays: `vertices` and `output`. Each vertex represents either a direct data query (`zingg_pipe`) or the output of a Zingg match job (`zingg_match`). Edges define how entities from different vertices connect. + +**Enterprise only.** The graph combination is a Zingg Enterprise feature. It requires Zingg Enterprise or Enterprise Plus. + +### Graph config structure + +```json +{ + "vertices": [ + { + "name": "spouse", + "vertexType": "zingg_pipe", + "data": [ + { + "name": "spouse", + "format": "snowflake", + "props": { + "query": "select a.id as id, a.FNAME, a.LNAME, a.STNO, a.ADD1, a.CITY, a.STATE, a.ZINGG_ID_PERSON, b.id as z_id, b.fname as Z_FNAME, b.lname as Z_LNAME, b.stno as Z_STNO, b.add1 as Z_ADD1, b.city as Z_CITY, b.state as Z_STATE, b.ZINGG_ID_PERSON as Z_ZINGG_ID_PERSON from CUSTOMER_RELATE_PARTIAL a, CUSTOMER_RELATE_PARTIAL b where a.familyId = b.familyId" + } + } + ], + "edges": { + "edgeType": "same_edge", + "edges": [ + { + "dataColumn": "zingg_personId", + "column": "zingg_personId", + "name": "zingg_personId1" + }, + { + "dataColumn": "zingg_personId", + "column": "z_zingg_personId", + "name": "zingg_personId2" + } + ] + } + }, + { + "name": "household", + "config": "$ZINGG_ENTERPRISE_HOME$/zinggEnterprise/configHousehold.json", + "strategy": { + "vDataStrategy": "unique_edge", + "props": { + "column": "zingg_personId", + "edge": "zingg_personId,z_zingg_personId" + } + }, + "vertexType": "zingg_match", + "edges": { + "edgeType": "same_edge", + "edges": [ + { + "dataColumn": "zingg_personId", + "column": "zingg_personId", + "name": "zingg_personId1" + }, + { + "dataColumn": "zingg_personId", + "column": "z_zingg_personId", + "name": "zingg_personId2" + } + ] + } + } + ], + "output": [ + { + "name": "relatedCustomers", + "format": "snowflake", + "props": { + "table": "RELATED_CUSTOMERS_PARTIAL" + } + } + ], + "strategy": "pairs_and_vertices" +} +``` + +#### **Understanding the config fields** + +* `vertexType: 'zingg_pipe'` - a direct data query. The `data` array specifies the source. Use this for exact-match relationships (e.g. shared family ID). +* `vertexType: 'zingg_match'` - the output of a full Zingg match model. The `config` field points to the model's config file. +* `edgeType: 'same_edge'` - connects records that represent the same entity across vertices. +* `strategy: 'pairs_and_vertices'` - the graph combination strategy. Determines how overlapping entity assignments are resolved. + +#### PYTHON API + +_**CHECK WITH SONAL \_ TEAM - Python API for combining match models is not documented in the reference PDF. Sonal to confirm before adding.**_ diff --git a/docs/recipes-and-integration/identity-rag-with-zingg-and-langchain.md b/docs/recipes-and-integration/identity-rag-with-zingg-and-langchain.md new file mode 100644 index 000000000..2cc47b2ac --- /dev/null +++ b/docs/recipes-and-integration/identity-rag-with-zingg-and-langchain.md @@ -0,0 +1,232 @@ +--- +description: >- + Use Zingg entity resolution to improve LLM accuracy in RAG pipelines by + resolving entity clusters before retrieval. +--- + +# Identity RAG with Zingg and LangChain + +Retrieval-Augmented Generation (RAG) has changed how large language models interact with domain-specific data. But traditional RAG approaches struggle with structured datasets that contain duplicates or name variations. + +Zingg solves this by clustering records that represent the same real-world entity before the data reaches the LLM. Each cluster receives a `Z_CLUSTER ID`. The LLM retrieves unified, consistent\ +information instead of fragmented duplicates. This approach is called Identity RAG. + +### Why identity resolution matters for RAG + +Without entity resolution, slight variations in names like "John Doe" vs "Jon Do"; or duplicate records from different source systems cause the retrieval layer to return inconsistent results. The LLM receives fragmented context and provides wrong or incomplete answers. + +Zingg clusters all records representing the same entity before they reach the vector store. The retriever finds all of them together. The LLM gives a complete, entity-aware answer. + +### How it works + +{% stepper %} +{% step %} +### Step 1: Run Zingg match + +Run the Zingg match phase on your dataset. Zingg clusters duplicate records and assigns each cluster a `Z_CLUSTER` in the output CSV. +{% endstep %} + +{% step %} +### Step 2: Import and preprocess + +Read the Zingg output CSV. Convert each record into a combined text string and store `Z_CLUSTER` as metadata. +{% endstep %} + +{% step %} +### Step 3: Embed and store + +Use an embedding model to create vector representations of each text string. Store them in a vector database with cluster metadata attached. +{% endstep %} + +{% step %} +### Step 4: Build the retrieval chain + +Create a `LangChain` retrieval chain that uses the vector store as the retriever and an LLM to generate structured responses. +{% endstep %} + +{% step %} +### Step 5: Query + +Invoke the chain with a natural language query. The LLM retrieves top matching records from the vector store and generates a response that respects entity cluster boundaries. +{% endstep %} +{% endstepper %} + +### Import and preprocess Zingg output + +```python +import pandas as pd + +df = pd.read_csv("zingg-out.csv") + +def preprocess(row): + text = ( + f"First Name: {row['FNAME']}, " + f"Last Name: {row['LNAME']}, " + f"Date of Birth: {row['DOB']}, " + f"Address: {row['STNO']} " + f"{row['ADD1']}, {row['ADD2']}, " + f"{row['AREA_CODE']} " + f"{row['STATE']}" + ) + metadata = { + "cluster": row["Z_CLUSTER"], + "ssn": row["SSN"], + "state": row["STATE"], + "dob": row["DOB"] + } + return {"text": text, "metadata": metadata} + +documents = [preprocess(row) for _, row in df.iterrows()] + +texts = [doc["text"] for doc in documents] +metadatas = [doc["metadata"] for doc in documents] +``` + +{% hint style="success" icon="right-long" %} +`zingg-out.csv` is your Zingg match output. `Z_CLUSTER` is the resolved entity identifier added\ +by Zingg during the match phase. Update the field names (FNAME, LNAME etc.) to match your own schema. + +**Read more**: For running the match phase - [Run the Match Phase](../running-zingg/run-the-match-phase.md). +{% endhint %} + +
Preprocess data with entity clustering

Preprocess data with entity clustering

+ +### Create embeddings and vector store + +{% hint style="success" icon="right-long" %} +This example uses Ollama with `nomic-embed-text` and Chroma as the vector store. Replace with your own embedding model and vector database as needed. +{% endhint %} + +```python +from langchain_ollama import OllamaEmbeddings +from langchain_chroma import Chroma + +embeddings = OllamaEmbeddings(model="nomic-embed-text") + +vector_store = Chroma.from_texts( + texts=texts, + embedding=embeddings, + metadatas=metadatas, + persist_directory="./chroma_db" +) + +retriever = vector_store.as_retriever(search_kwargs={"k": 2}) +``` + +### Build the LangChain chain + +The LangChain chain combines the retriever, an Ollama LLM, a prompt template, and an output parser. The prompt template tells the LLM how to behave. Those instructions are shown below as prose; then the chain itself is shown as Python. + +#### **Prompt instructions (paste into your template)** + +You are an identity resolution expert. Analyse the cluster information to: + +1. Match records based on name, DOB, and other attributes +2. Handle typos and name variations (for example, "Schulz" vs "Schultz") +3. Compare DOBs in YYYY-MM-DD format +4. If no exact match, show closest candidates with differences +5. Always mention Cluster ID and record count + +Format the response as: + +* Query +* Closest Match +* Cluster ID | Record Count | Match Confidence +* Details (differences between records) + +#### **Chain construction** + +```python +from langchain_core.prompts import ChatPromptTemplate +from langchain_ollama import ChatOllama +from langchain_core.runnables import RunnablePassthrough +from langchain_core.output_parsers import StrOutputParser + +template = "" + +prompt = ChatPromptTemplate.from_template(template) +llm = ChatOllama(model="llama3:8b") + +chain = ( + {"context": retriever, "question": RunnablePassthrough()} + | prompt + | llm + | StrOutputParser() +) +``` + +### Query the chain + +#### **Query 1: exact name and DOB match** + +The query searches for records related to "benjamin koerbin" born on "19210210" using the identity-aware retrieval system. + +```python +response = chain.invoke( + "Find records of benjamin koerbin " + "born on 19210210" +) +print(response) +``` + +The system successfully retrieves two matching records from the same cluster (Cluster ID 21) with 100% match confidence, confirming both documents correspond to the queried entity without discrepancies. The response shows the query, the closest match, the Cluster ID, record count, match confidence, and that exact matches were found on both name and date of birth. + +{% hint style="success" icon="right-long" %} +In Query 2 the system identifies the correct cluster even though "`eling`" is a typo for "`eglinton`". Zingg grouped both spellings into the same cluster before the data reached the vector store, so the retriever found all matching records regardless of the variation. +{% endhint %} + +#### **Query 2: typo tolerance with fuzzy matching** + +The query searches for records of "jakson eling," which contains a potential typo in the last name. + +```json +response = chain.invoke("Find records of jakson eling") +print(response) +``` + +The system identifies and retrieves three matching records under the same cluster, despite the typo in the query. It correctly maps `eling` to `eglinton` using fuzzy matching, ensuring accurate identity resolution. + +{% hint style="success" icon="right-long" %} +Zingg grouped both spellings (`eglinton` and `eling`) into the same cluster during the match phase - before the data reached the vector store. The retriever finds all matching records regardless of input variation. +{% endhint %} + +### Use cases + +
+ +Customer service chatbots + +Resolve customer identities across fragmented databases so the chatbot retrieves complete, unified records regardless of how the customer name was entered across different channels. + +
+ +
+ +Healthcare systems + +Link patient records from multiple providers under a single cluster. The LLM retrieves complete patient context rather than fragmented records from different systems. + +
+ +
+ +Financial compliance + +Detect duplicate transactions or accounts for fraud prevention. Zingg clusters related records, and the LLM reasons over a unified entity context. + +
+ +
+ +E-commerce + +Unify customer profiles from different channels for personalized recommendations. The LLM retrieves the complete customer entity rather than individual fragmented records. + +
+ +{% hint style="success" icon="right-long" %} +**Read more:** + +* For the full Zingg workflow - [How Zingg works](../running-zingg/step-by-step-guide.md) +* Download the notebook from the Zingg GitHub repository: `github.com/zinggAI/zingg` +{% endhint %} diff --git a/docs/recipes-and-integration/pre-trained-models.md b/docs/recipes-and-integration/pre-trained-models.md new file mode 100644 index 000000000..62e7cd1cb --- /dev/null +++ b/docs/recipes-and-integration/pre-trained-models.md @@ -0,0 +1,137 @@ +--- +description: >- + Use a Zingg pre-trained model to start matching without going through the + findTrainingData and label phases. +--- + +# Pre-trained Models + +Zingg ships with a pre-trained model built on the `FEBRL` dataset; for example, a synthetic Australian population dataset with name, address, date of birth, and SSN fields. The `FEBRL` pre-trained model is bundled in the Zingg Docker image and available from the Zingg GitHub\ +repository. + +Use a pre-trained model when you want to run a quick proof of concept on data that matches the `FEBRL` schema, or as a starting point that you fine-tune with your own labels. + +### Using the FEBRL pre-trained model + +#### Step 1: Download the pre-trained model + +The FEBRL pre-trained model is bundled in the Zingg Docker image. It is available in the models/ directory inside the container. + +```bash +docker pull zingg/zingg:0.5.0 +docker run -it zingg/zingg:0.5.0 bash +``` + +Inside the container, models are at: + +``` +/zingg/models/ +``` + +{% hint style="success" icon="right-long" %} +Pre-trained models are also available from the Zingg GitHub releases page: `github.com/zinggAI/zingg/releases` +{% endhint %} + +#### Step 2: Use the pre-trained model in your config + +```json +{ + "fieldDefinition": [ + { + "fieldName": "fname", + "matchType": "fuzzy", + "fields": "fname", + "dataType": "string" + }, + { + "fieldName": "lname", + "matchType": "fuzzy", + "fields": "lname", + "dataType": "string" + }, + { + "fieldName": "dob", + "matchType": "exact", + "fields": "dob", + "dataType": "string" + }, + { + "fieldName": "ssn", + "matchType": "exact", + "fields": "ssn", + "dataType": "string" + } + ], + "data": [ + { + "name": "yourInput", + "format": "csv", + "props": { + "location": "path/to/your/data.csv", + "delimiter": ",", + "header": "true" + } + } + ], + "output": [ + { + "name": "yourOutput", + "format": "csv", + "props": { + "location": "/tmp/zinggOutput" + } + } + ], + "modelId": "100", + "zinggDir": "models", + "labelDataSampleSize": 0.5, + "numPartitions": 4 +} +``` + +#### Step 3: Run match directly (no training needed) + +**Python** + +```python +options = ClientOptions([ClientOptions.PHASE, "match"]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +**CLI** + +Skip `findTrainingData` and `label`. Run `match` directly using the pre-trained model. + +```bash +./zingg.sh --phase match --conf config.json +``` + +{% hint style="success" icon="right-long" %} +The pre-trained `FEBRL` model works best on data with the same field schema: `fname`, `lname`, `stNo`,\ +`add1`, `add2`, `city`, `areacode`, `state`, `dob`, `ssn`. + +For significantly different schemas or data distributions, fine-tuning with your own labels will improve accuracy. +{% endhint %} + +### Fine-tuning a pre-trained model + +You can use a pre-trained model as a starting point and improve it with your own labeled data. Run `findTrainingData` and `label` on your own dataset, then run train. Zingg will continue training using the pre-trained model weights rather than starting from scratch. + +This is faster than building a model from scratch when your data is structurally similar to the pre-trained model's training data. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* Create training data - [Create training data](../running-zingg/create-training-data.md) +* Label training pairs - [Label training pairs](../running-zingg/label-training-pairs.md) +* Build and save the model - [Build and save the model](../running-zingg/build-and-save-the-model.md) +{% endhint %} + +### Other available pre-trained models + +Zingg ships with pre-trained models for several datasets. All models are bundled under the `models/` directory in the Zingg installation. Check `models/modelIds.txt` for the complete list of available models and their IDs. + +
ModelDatasetFields
FEBRLSynthetic Australian populationfname, lname, stNo, add1, add2, city, areacode, state, dob, ssn
North Carolina VotersUS voter registration recordsName, address, date of birth
Amazon-Google ProductsProduct catalog matchingProduct title, description
+ +For schemas and model IDs, see `models/modelIds.txt` inside your Zingg installation. diff --git a/docs/reference/cli-command-reference.md b/docs/reference/cli-command-reference.md new file mode 100644 index 000000000..a7fd2ac42 --- /dev/null +++ b/docs/reference/cli-command-reference.md @@ -0,0 +1,117 @@ +--- +description: All Zingg CLI phase commands and flags in one place. +--- + +# CLI Command Reference + +### Basic invocation pattern + +```bash +./scripts/zingg.sh --phase --conf +``` + +```bash +./scripts/zingg.sh --run +``` + +Use `--run` to execute a Python program through `zingg.sh`. Zingg Python programs are PySpark programs. The CLI executes both JSON config jobs and Python program jobs. It is not a replacement for the Python API; it is the runtime that executes both. + +### All phases + +
Phase nameEditionDescription
findTrainingDataAllScans the dataset and selects candidate pairs for labeling.
labelAllOpens interactive labeling session. Run after
findTrainingData.
generateDocsAllGenerates HTML documentation of labeled
training data for review.
trainAllBuilds blocking and similarity models using labeled training data.
matchAllApplies trained models to the full dataset. Writes clusters to output.
linkAllMatches records across two separate datasets. Same model as match.
recommendAllGenerates stopword candidates for a specified field.
verifyBlockingEnterpriseChecks blocking model coverage against known
matching pairs.
trainMatchEnterpriseCombined train and match in one phase call.
findAndLabelEnterpriseCombined findTrainingData and label in one phase call.
runIncrementalEnterpriseUpdates identity graph with new or changed
records without a full re-match.
explainOutputEnterpriseShows pair-level evidence for how a specific cluster was formed.
generateDocsAllGenerates HTML documentation of model and data statistics.
reassignZinggIdEnterprise PlusReassigns Zingg IDs when switching
to a new trained model.
updateLabelAllRevisit and update previously marked training pairs. Run before train to correct labeling errors. Run generateDocs first to identify pairs to update.
diffEnterpriseCompare two model outputs to understand exactly what changed between them before deploying. Identifies which clusters merged, split, or moved.
runLookupEnterpriseLook up specific records in existing match output to find which entity cluster they belong to and get their Zingg ID
+ +### All flags + +
FlagRequired/OptionalDescription
--phaseRequiredThe Zingg phase is to run. See phases table above.
--confRequiredPath to your JSON config file.
--properties-fileOptionalPath to Zingg runtime properties
file (zingg.conf). This is required for Snowflake and some cloud connectors.
--zinggDirOptionalOverride the zinggDir from config. Useful
for S3 and GCS paths.
--showConciseOptionaltrue or false. When true, hides
DONT_USE fields in the label phase
terminal display.
--columnRequired with recommendThe field name to generate stopword
recommendations for.
--originalZinggIdRequired with
reassignZinggId
Path to the original production config file.
--runOptionalPath to a Python program to execute through zingg.sh. Use instead of --phase and --conf when running Python-based Zingg programs.
--zinggidRequired with explainOutputThe Zingg ID of the cluster to explain. Find Zingg IDs in your match output ZINGG_ID column.
+ +### Example commands + +#### **`findTrainingData`** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` + +#### **`label` (with concise display)** + +```bash +./scripts/zingg.sh --phase label --conf config.json --showConcise=true +``` + +#### **`generateDocs`** + +```bash +./scripts/zingg.sh --phase generateDocs --conf config.json --showConcise=true +``` + +#### **`train`** + +```bash +./scripts/zingg.sh --phase train --conf config.json +``` + +#### **`match`** + +```bash +./scripts/zingg.sh --phase match --conf config.json +``` + +#### **`link`** + +```bash +./scripts/zingg.sh --phase link --conf config.json +``` + +#### **`recommend` (stopwords for `fname`)** + +```bash +./scripts/zingg.sh --phase recommend --conf config.json --column fname +``` + +#### **`runIncremental`** + +```bash +./scripts/zingg.sh --phase runIncremental --conf incrementalConf.json +``` + +#### **`reassignZinggId`** + +```bash +./scripts/zingg.sh --phase reassignZinggId --conf configReassign.json --originalZinggId config.json --properties-file config/zingg.conf +``` + +#### **`updateLabel`** + +```bash +./scripts/zingg.sh --phase updateLabel --conf config.json +``` + +#### `findAndLabel` (combined - smaller datasets) + +```bash +./scripts/zingg.sh --phase findAndLabel --conf config.json +``` + +#### `verifyBlocking` (run after label) + +```bash +./scripts/zingg.sh --phase verifyBlocking --conf config.json +``` + +#### **`diff` (compare two model outputs)** + +```bash +./scripts/zingg.sh --phase diff --conf configNew.json --compareTo configBaseline.json --properties-file config/zingg.conf +``` + +#### **`explainOutput` (explain a specific cluster)** + +```bash +./scripts/zingg.sh --phase explainOutput --zinggid ea67d79a-56a7-4431-ab55-d08bb3c10e2e --conf explainConfig.json +``` + +#### **trainMatch (combined - Enterprise)** + +```bash +./scripts/zingg.sh --phase trainMatch --conf config.json +``` diff --git a/docs/reference/configuration-schema.md b/docs/reference/configuration-schema.md new file mode 100644 index 000000000..ee9fc2402 --- /dev/null +++ b/docs/reference/configuration-schema.md @@ -0,0 +1,98 @@ +--- +description: >- + Complete reference for all Zingg JSON configuration parameters and Python API + arguments. +--- + +# Configuration Schema + +This page is the reference. Every Zingg configuration parameter, JSON key and Python API method is documented here with type, valid values, and edition availability. + +{% hint style="success" icon="right-long" %} +**Read more**: For the step-by-step task of setting up configuration - [Configure Zingg](../running-zingg/configure-zingg.md). +{% endhint %} + +### Top-level parameters + +
ParameterTypeEditionDescription
modelIdstringAll editionsUnique identifier for this model. Used as folder name under zinggDir. Use the same modelId across all phases for a given run.
zinggDirstringAll editionsRoot directory where Zingg writes model files and training data. Can be a local path, DBFS path (dbfs:/), GCS path (gs://), S3 path (s3a://), or OneLake path (abfss://).
numPartitionsintegerAll editionsNumber of Spark partitions. Controls how data is distributed across cluster nodes. Rule of thumb: set to approximately 20–30× the number of worker vCPUs. Start with 4–8 for a standard development cluster. Increase proportionally for larger datasets.
labelDataSampleSizefloat (0.0001–0.1)All editionsFraction of the dataset scanned when running findTrainingData. Valid range: 0.0001 to 0.1. For 100k records use 0.1–0.5. For 1M+ records use 0.01–0.05. Reduce to 0.05 or lower if findTrainingData is slow on large datasets.
stopWordsCutofffloat (0–1)All editions, optionalUsed with the recommend phase. Fraction of high-frequency words to extract as stopword candidates. Default is 0.1 (10%).
collectMetricsbooleanAll editionsControls telemetry collection. Default true. Set to false to disable. When enabled, Zingg captures runtime metrics (record count, field count, running phase, execution time). No input data or user data is ever captured. See Security and Privacy for full details.
passthroughExprstringEnterprise only, optionalSQL expression defining records to exclude from matching. Records matching this expression are carried through to output with their own Zingg ID but do not influence cluster formation. Example: "fname = 'matilda'".
deterministicMatchingarrayEnterprise only, optionalDeterministic match conditions, where exact field matches always result in a match regardless of the probabilistic score. See Deterministic vs Probabilistic Matching for full configuration.
setBlockingModelstringEnterprise only, optionalBlocking strategy. Valid values: "DEFAULT", "WIDER". If not set, the model uses DEFAULT.
+ +### `fieldDefinition` + +Each entry in the `fieldDefinition` array defines one field in your input schema. + +
ParameterTypeEditionDescription
fieldNamestringAll editionsName of the field in your input dataset. Must match exactly.
matchTypeenumAll editionsHow to compare this field. See Match Types for all values.
fieldsstringAll editionsThe field to use for comparison. Keep the same as fieldName for standard use. Can reference a different column name if the field you want to compare appears under a different name in the input schema. For now, keep this the same as fieldName unless specifically advised otherwise.
dataTypestringAll editionsSpark SQL data type - string, integer, double, date, timestamp.
stopWordsstring (path)All editions, optionalPath to a CSV file of stopwords for this field. One word per row. The file must have a header row. Generated by the recommend phase.
primaryKeybooleanEnterprise only, optionalMarks this field as the primary key. Used by runIncremental to identify records uniquely across runs and by reassignZinggId for ID preservation. Set true on exactly one field per config.
postProcessorsstringEnterprise only, optionalStandardise postprocessor reference. Format: STANDARDISE_<basename> where <basename>.json is the mapping file. Normalises field values to canonical form after matching.
MappingMatchTypenested objectEnterprise only, optionalUser-supplied lookup file for nickname/abbreviation matching. See MAPPING match type for the mapping file format and rules.
+ +### `data` (input pipe) + +
ParameterTypeEditionDescription
namestringAll editionsLogical name for this pipe. Used in logging.
formatstringAll editionsConnector format. See Connect Your Data for all supported formats.
propsobjectAll editionsConnector-specific properties. Keys depend on the format. See Connect Your Data for props per connector.
schemastringAll editionsSpark SQL schema string. Required for CSV. Format: "field1 type1, field2 type2, ...".
+ +### `output` (output pipe) + +
ParameterTypeEditionDescription
namestringAll editionsLogical name for this output pipe.
formatstringAll editionsConnector format. See Connect Your Data for supported output formats.
propsobjectAll editionsConnector-specific properties. Keys depend on the format.
+ +### `outputStats` (statistics pipe) + +Uses the same `format`, `name`, and `props` pattern as `data` and `output`. The path must contain the `$ZINGG_DYNAMIC_STAT_NAME` placeholder, which is replaced at runtime with `SUMMARY`, `CLUSTER`, or `RECORD` depending on the statistics type written. If not configured, statistics are not written and the run proceeds normally. + +
ParameterTypeEditionDescription
namestringEnterprise onlyLogical name for this stats pipe.
formatstringEnterprise onlyConnector format. Usually csv.
props.locationstringEnterprise onlyPath must contain $ZINGG_DYNAMIC_STAT_NAME. Example: /tmp/zinggStats_$ZINGG_DYNAMIC_STAT_NAME
+ +### JSON Config + +A complete JSON config combines top-level parameters, `fieldDefinition`, `data`, and `output`, plus `outputStats` for Enterprise. + +#### **Using environment variables in JSON config** + +If you do not want to pass sensitive values (passwords, API keys, connection strings) through the config file directly, you can configure them through system environment variables. Wrap the variable name in dollar signs in your config: + +* **Strings:** `"$var$"` (with quotes) +* **Booleans and numerics:** `$var$` (without quotes) + +At runtime, Zingg replaces the placeholder with the actual environment variable value. + +**Example JSON config with environment variables** + +```json +{ + "output": [ + { + "name": "unifiedCustomers", + "format": "net.snowflake.spark.snowflake", + "props": { + "path": "$location$", + "password": "$passwd$" + } + } + ], + "labelDataSampleSize": 0.5, + "numPartitions": 4, + "modelId": "$modelId$", + "zinggDir": "models", + "collectMetrics": "$collectMetrics$" +} +``` + +{% hint style="success" icon="right-long" %} +Environment variable substitution applies anywhere in the JSON config - top-level parameters, pipe `props`, field paths, output locations, and credentials. The substitution is purely textual at runtime, so any environment variable Zingg can read can be referenced this way. +{% endhint %} + +### Python API equivalents + +For every JSON parameter, there is an equivalent Python API method on the `Arguments` (Community) or `EArguments` (Enterprise) object. + +
JSON parameterPython API methodEditionExample
modelIdargs.setModelId()All editionsargs.setModelId("100")
zinggDirargs.setZinggDir()All editionsargs.setZinggDir("/tmp/models")
numPartitionsargs.setNumPartitions()All editionsargs.setNumPartitions(4)
labelDataSampleSizeargs.setLabelDataSampleSize()All editionsargs.setLabelDataSampleSize(0.5)
fieldDefinitionargs.setFieldDefinition([])All editionsargs.setFieldDefinition(fieldDefs)
dataargs.setData()All editionsargs.setData(inputPipe)
outputargs.setOutput()All editionsargs.setOutput(outputPipe)
collectMetricsargs.setCollectMetrics()All editionsargs.setCollectMetrics(False)
outputStatsargs.setOutputStats()Enterprise onlyargs.setOutputStats(statsPipe)
passthroughExprargs.setPassthroughExpr()Enterprise onlyargs.setPassthroughExpr("fname = 'matilda'")
deterministicMatchingargs.setDeterministicMatchingCondition()Enterprise onlyargs.setDeterministicMatchingCondition(dm1, dm2)
setBlockingModelargs.setBlockingModel()Enterprise onlyargs.setBlockingModel("DEFAULT")
primaryKey (field-level)fieldDef.setPrimaryKey()Enterprise onlyrecId.setPrimaryKey(True)
postProcessors (field-level)fieldDef.setPostProcessors()Enterprise onlyjob_title.setPostProcessors([StandardisePostprocessorType("STANDARDISE", "jobtitles")])
+ +{% hint style="success" icon="right-long" %} +Sample config files: + +* `github.com/zinggAI/zingg/tree/main/ examples/febrl/config.json` +* `github.com/zinggAI/zingg/tree/main/examples/febrl120k/config.json` +{% endhint %} + +{% hint style="success" icon="right-long" %} +**Related pages:** + +* [Configure Zingg](../running-zingg/configure-zingg.md) - step-by-step task of building your config +* [Match Types](../zingg-concepts/how-zingg-learns/match-types/) - full reference for all `matchType` values +* [Connect Your Data](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/connect-your-data) - all `data` and `output` pipe formats and props +* [Working With Python](../zingg-python-api/working-with-python.md) - Python API alternative to JSON config +{% endhint %} diff --git a/docs/reference/hardware-sizing-and-benchmarks.md b/docs/reference/hardware-sizing-and-benchmarks.md new file mode 100644 index 000000000..144a61c50 --- /dev/null +++ b/docs/reference/hardware-sizing-and-benchmarks.md @@ -0,0 +1,43 @@ +--- +description: >- + Hardware required for different sizes of data. Real-world benchmarks across + local Spark, AWS, and large clusters to help you size your Zingg deployment. +--- + +# Hardware Sizing and Benchmarks + +Zingg has been built to scale. Performance depends on three factors: + +* The number of records to be matched +* The number of fields to be compared against each other +* The actual number of duplicates in your data + +Use the benchmarks below as starting points when sizing your Zingg deployment. Your actual runtime will vary based on data complexity, field types, blocking strategy, and the match types in your configuration. + +### Benchmarks + +Real-world Zingg runs across different dataset sizes and hardware: + +
RecordsFieldsHardwareRuntime
120kfebrl test schema4 cores, 10 GB RAM, local Spark5 minutes
5m (North Carolina Voters)voter schema4 cores, 10 GB RAM, local Spark~4 hours
9m3 fields (first name, last name, email)AWS m5.24xlarge - 96 cores, 384 GB RAM45 minutes
80m8 to 10 fields1 driver (128 GB RAM, 32 cores) + 8 workers (224 GB RAM, 64 cores)Less than 2
+ +### When to use Spark local mode + +For up to a few million records, it may be easier to run Zingg on a single machine in Spark local mode rather than provisioning a cluster. The first two benchmarks above (120k and 5m records) ran in this mode. + +The threshold to move from local mode to a cluster is roughly: + +* Up to \~5 million records - local mode on a single machine works well +* Above \~5 million records - cluster mode (Databricks, Fabric, EMR, or self-managed Spark) recommended +* Above \~50 million records - multi-worker cluster strongly recommended + +### Docker for local development + +For local development, proof of concept work, and validating the match workflow before sizing production hardware, the Zingg Docker image runs on any machine with Docker installed. No cluster setup required. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* [Quick Start](../running-zingg/quick-start-docker.md) - run Zingg locally via Docker +* [Install Zingg](../running-zingg/install-zingg.md) - full installation options across platforms +* [Configure Zingg](../running-zingg/configure-zingg.md) - `numPartitions` and partitioning configuration that affect performance" +{% endhint %} diff --git a/docs/reference/reading-material.md b/docs/reference/reading-material.md new file mode 100644 index 000000000..abb2d5b09 --- /dev/null +++ b/docs/reference/reading-material.md @@ -0,0 +1,27 @@ +--- +description: >- + Important reading materials to help you understand additional details about + the entity resolution space. +--- + +# Reading Material + +Entity Resolution and The Modern Data Stack: + +* [From Rows to People](https://roundup.getdbt.com/p/from-rows-to-people) + +Identity Resolution and Why CDPs fail: + +* [Customer Journey](https://www.gartner.com/en/articles/customer-journey) + +Entity Resolution using a graph database: + +* [Entity Resolution with TigerGraph](https://towardsdatascience.com/entity-resolution-with-tigergraph-add-zingg-to-the-mix-95009471ca02) + +A detailed write-up on entity resolution - the problem, its challenges, and applications: + +* [Entity Resolution](https://towardsdatascience.com/an-introduction-to-entity-resolution-needs-and-challenges-97fba052dde5) + +Understanding Master Data and Master Data Management: + +* [Agile Data Mastering](https://towardsdatascience.com/a-guide-to-agile-data-mastering-with-ai-3bf38f103709) diff --git a/docs/reference/runtime-properties.md b/docs/reference/runtime-properties.md new file mode 100644 index 000000000..bfc06ad30 --- /dev/null +++ b/docs/reference/runtime-properties.md @@ -0,0 +1,102 @@ +--- +description: >- + Zingg runtime properties file (zingg.conf) reference - Spark config, JAR + paths, and connector-specific settings. +--- + +# Runtime Properties + +The `zingg.conf` file sets Spark configuration properties that Zingg passes to spark-submit at runtime. It lives in the `config/` directory of your Zingg installation and is referenced via the `properties-file` flag. + +### Core Spark properties + +```bash +spark.master=local[*] +spark.executor.memory=4g +spark.driver.memory=4g +spark.sql.shuffle.partitions=200 +``` + +
PropertyDefaultDescription
spark.masterlocal[*]Spark master URL. Use local[*] for local development (all cores). For cluster mode, set to your cluster manager URL. On managed platforms (Databricks, Fabric, EMR) this is managed automatically; do not override.
spark.executor.memory4gMemory allocated per executor. Increase if you see OOM errors during training or matching. For large datasets (1M+ records) try 8g or 16g.
spark.driver.memory4gMemory allocated to the Spark driver. Usually matches or is slightly lower than spark.executor.memory.
spark.sql.shuffle.partitions200Number of partitions used during shuffle operations. Increase for very large datasets during the match phase. A common value for large datasets is 400800.
+ +### Adding JARs for connectors + +Separate multiple JARs with commas. + +#### Snowflake + +```bash +spark.jars=snowflake-jdbc-3.13.19.jar,spark-snowflake_2.12-2.10.0-spark_3.1.jar +``` + +#### BigQuery + +```bash +spark.jars=spark-bigquery-with-dependencies_2.12-0.24.2.jar,gcs-connector-hadoop2-latest.jar +``` + +#### BigQuery Hadoop Property + +```bash +spark.hadoop.fs.gs.impl=com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem +``` + +#### AWS S3 + +```bash +spark.jars=hadoop-aws-3.1.0.jar,aws-java-sdk-bundle-1.11.271.jar +``` + +#### JDBC (replace with your driver) + +```bash +spark.jars=postgresql-42.3.1.jar +``` + +{% hint style="danger" icon="right-long" %} +JAR versions above are illustrative. Check the Zingg GitHub releases page for the verified versions tested with each Zingg release: `github.com/zinggAI/zingg/releases` . +{% endhint %} + +### GCP Dataproc Properties + +For GCP Dataproc, inject JARs via cluster creation properties. + +**Key**: spark.jars + +**Value**: + +* `gs://BUCKET/zingg-0.6.0.jar` +* `gs://BUCKET/spark-3.5-bigquery-0.44.1.jar` +* `gs://BUCKET/gcs-connector-hadoop3-latest.jar` + +On GCP Dataproc, JARs are injected at cluster creation time via cluster properties rather than in `zingg.conf`. The `spark.jars` key in the cluster properties accepts a comma-separated GCS path list. + +#### Enterprise Snowflake Properties + +The `zingg.conf` equivalent is the `snowEnv.txt` properties file. It sets Snowflake connection properties rather than Spark properties. + +```bash +URL={snowflake_url} +USER={snowflake_user_name} +PASSWORD={snowflake_password} +ROLE={role} +WAREHOUSE={warehouse} +DB={database_name} +SCHEMA={schema} +CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY=900 +``` + +Pass this file via `--properties-file` when running any Zingg phase on Snowflake. + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json --properties-file snowEnv.txt +``` + +`CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY`: seconds between client attempts to update the session token. Valid range: `900` to `3600`. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* For connector-specific JAR setup per platform - [Connect Data](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/connect-your-data) +* For platform-specific runtime config - [Run on Cloud](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/platform-guides) +{% endhint %} diff --git a/docs/reference/zingg-command-line.md b/docs/reference/zingg-command-line.md new file mode 100644 index 000000000..3ec525677 --- /dev/null +++ b/docs/reference/zingg-command-line.md @@ -0,0 +1,209 @@ +--- +description: >- + The two CLI invocation patterns and copy-paste commands for every Zingg phase + for Docker, local Spark, EMR, and CI/CD environments. +--- + +# Zingg Command Line + +The CLI lets you invoke Zingg phases from any environment that has a terminal - Docker containers, local Spark installations, AWS EMR clusters, or CI/CD pipelines. No notebook required. + +You can pass a JSON configuration file directly or point the CLI at a Python program to run. This means you can run Zingg Python programs through `zingg.sh` just as easily as JSON-configured jobs - the CLI is not a replacement for the Python API; it is the runtime that executes both. + +### The Two Invocation Patterns + +**Pass a JSON config file** + +```bash +./scripts/zingg.sh --phase --conf +``` + +**Run a Python program** + +```bash +./scripts/zingg.sh --run +``` + +{% hint style="success" icon="right-long" %} +Full command reference with all flags and options - [CLI command reference](cli-command-reference.md) +{% endhint %} + +{% tabs %} +{% tab title="Community" %} +{% hint style="danger" icon="right-long" %} +Replace `config.json` with your actual config file path. +{% endhint %} + +### **Find training pairs** + +```bash +./zingg.sh --phase findTrainingData --conf config.json +``` + +### **Label pairs** + +The `--showConcise=true` flag shows only fields used for matching and hides `DONT_USE` fields. + +```bash +./zingg.sh --phase label --conf config.json --showConcise=true +``` + +### Train the model + +```bash +./zingg.sh --phase train --conf config.json +``` + +### **Run match** + +`match` finds duplicates within a single dataset. + +```bash +./zingg.sh --phase match --conf config.json +``` + +### **Run link** + +`link` matches records across two separate datasets. + +```bash +./zingg.sh --phase link --conf config.json +``` +{% endtab %} + +{% tab title="Enterprise" %} +{% hint style="info" icon="right-long" %} +Enterprise adds two phases not available in Community: `generateDoc`s and\ +`runIncremental`. Replace `config.json` with your actual config file path. +{% endhint %} + +### Find training pairs + +```bash +./zingg.sh --phase findTrainingData --conf config.json +``` + +### Label pairs + +```bash +./zingg.sh --phase label --conf config.json --showConcise=true +``` + +### **Generate model documentation** + +Optional but recommended before `train` — lets you inspect training data quality before committing to the train phase. + +```bash +./zingg.sh --phase generateDocs --conf config.json +``` + +### Train the model + +```bash +./zingg.sh --phase train --conf config.json +``` + +### Run match + +```bash +./zingg.sh --phase match --conf config.json +``` + +### Run link + +```bash +./zingg.sh --phase link --conf config.json +``` + +### **Run incremental** + +Updates the identity graph with new and changed records without re-running the full match. + +```bash +./zingg.sh --phase runIncremental --conf config.json +``` + +{% hint style="info" icon="right-long" %} +`generateDocs`, `runIncremental`, and `diff` are Enterprise only phases. +{% endhint %} +{% endtab %} +{% endtabs %} + +
+ +When should I use link instead of match? + +Use `match` when your goal is to find duplicates within a single dataset. For example, identifying that two customer records in the same system represent the same person. + +Use `link` when you have two separate datasets that are individually duplicate-free but need to be matched against each other. For example, this involves matching a supplier list from one system against a vendor master from another. Each record from the first source is matched with all records from the remaining sources. The output includes a `z_sourc`e column that identifies which source dataset each record came from. + +Both `match` and `link` use the same trained model. They are equal operations, not a hierarchy. + +
+ +
+ +What does a basic config.json look like? + +The JSON config file tells Zingg which fields to use, where your data lives, and where to write output. Here is the minimum structure to run any phase: + +```json +{ + "fieldDefinition": [ + { + "fieldName": "fname", + "matchType": "FUZZY", + "fields": "fname", + "dataType": "string" + }, + { + "fieldName": "lname", + "matchType": "FUZZY", + "fields": "lname", + "dataType": "string" + }, + { + "fieldName": "dob", + "matchType": "EXACT", + "fields": "dob", + "dataType": "string" + } + ], + "data": [ + { + "name": "inputData", + "format": "csv", + "props": { + "location": "/path/to/input.csv", + "delimiter": ",", + "header": "true" + } + } + ], + "output": [ + { + "name": "matchOutput", + "format": "csv", + "props": { + "location": "/path/to/output", + "delimiter": ",", + "header": "true" + } + } + ], + "modelId": "1", + "zinggDir": "/tmp/zingg", + "labelDataSampleSize": 0.5, + "numPartitions": 4 +} +``` + +{% hint style="success" icon="right-long" %} +**Read more**: + +* Full configuration schema with all parameters and options - [Configuration schema](configuration-schema.md) +* Full flag and options reference - [CLI command reference](cli-command-reference.md) +* Python API alternative - [Python API](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/zingg-python-api) +{% endhint %} + +
diff --git a/docs/running-zingg/build-and-save-the-model.md b/docs/running-zingg/build-and-save-the-model.md new file mode 100644 index 000000000..f45752e59 --- /dev/null +++ b/docs/running-zingg/build-and-save-the-model.md @@ -0,0 +1,72 @@ +--- +description: >- + Run the training phase to build Zingg's matching model from your labeled + training data and save it for reuse. +--- + +# Build and Save the Model + +The training phase builds up the Zingg models using the training data from your label sessions and writes them to `zinggDir/modelId` as specified in your config. + +Once saved, reuse the same `modelId` in all subsequent phases - `match`, `link`, and `runIncremental` to apply this trained model to your data. + +{% hint style="success" icon="right-long" %} +Model saved to: `zinggDir/modelId` + +Use the same `modelId` when running `match`, `link`, or `runIncremental` to apply this trained model. +{% endhint %} + +{% tabs %} +{% tab title="Community" %} +### Python + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "train" +]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +### CLI + +```bash +./zingg.sh --phase train --conf config.json +``` +{% endtab %} + +{% tab title="Enterprise" %} +### Python + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "train" +]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +### CLI + +```bash +./zingg.sh --phase train --conf config.json +``` + +Enterprise adds blocking model configuration via `args.setBlockingModel()`. Set this in Configure Zingg before running `train`. + +Available options: + +* `DEFAULT` - standard blocking strategy, suitable for most datasets +* `WIDER` - casts a wider blocking net; use if known matching pairs are being missed +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +{% hint style="info" icon="right-long" %} +Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark cluster. +{% endhint %} + +_**CHECK WITH SONAL ABOUT THE ENTIRE SNOWFLAKE RELATED CONTENT**_ +{% endtab %} +{% endtabs %} diff --git a/docs/running-zingg/cluster-approval.md b/docs/running-zingg/cluster-approval.md new file mode 100644 index 000000000..960ffc3c1 --- /dev/null +++ b/docs/running-zingg/cluster-approval.md @@ -0,0 +1,45 @@ +--- +description: >- + Manually review and approve or reject matched clusters before committing them + to the identity graph. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# Cluster Approval + +{% hint style="info" icon="right-long" %} +Enterprise only. Manual review and sign-off of matched clusters before committing to the identity graph. Approved decisions are preserved across incremental runs and never overridden. +{% endhint %} + +After Zingg runs the match or incremental phase, some clusters may need human review before they are committed to the identity graph. Cluster approval gives data stewards and domain experts the ability to review Zingg's match decisions, approve the ones that are correct, and reject the ones that are wrong. + +Approved and rejected decisions are stored by Zingg and preserved across subsequent incremental runs. Zingg will not override a human-approved or human-rejected cluster decision\ +in future runs, the human decision always takes precedence. + +_**CHECK WITH SONAL—The cluster approval page currently shows "Coming Soon" on the live docs with no technical content. All content above is written from KT notes and the incremental page context. Please review and confirm**_ + +### When to use cluster approval + +Records are high-stakes, and errors have downstream consequences. For example, a financial compliance system that links counterparties to sanctions lists or a KYC system where a false match could affect a customer's account. + +Subject matter experts have domain knowledge that Zingg's model does not. For example, two companies with similar names that are actually separate legal entities, or two individuals who share a name and address but are different people. + +You need an audit trail of human decisions for governance or regulatory purposes. + +### How cluster approval works + +1. Run the match or incremental phase to generate clusters. +2. Review the clusters flagged for approval; either all clusters or a subset based on score thresholds or cluster size. +3. For each cluster, decide: Approve (the records are correctly grouped) or Reject (the records should not be in the same cluster). +4. Approved clusters are committed to the identity graph with their Zingg IDs. Rejected clusters are split, and their records are treated as separate entities. +5. In subsequent incremental runs, Zingg respects your approval decisions. A record that was manually approved as part of a cluster will not be removed from that cluster in a future run. + +### Effect on the identity graph + +* **Approved clusters**: The Zingg ID is locked to those records. Future incremental runs will keep a manually approved Zingg ID assigned unless a human explicitly rejects it. +* **Rejected clusters**: The records that were incorrectly grouped are separated. They may receive new Zingg IDs in the next incremental run if they match other clusters or remain as independent entities with their own IDs. + +This makes cluster approval the governance layer on top of Zingg's automated entity resolution - human decisions take precedence over model decisions. diff --git a/docs/running-zingg/compare-model-results.md b/docs/running-zingg/compare-model-results.md new file mode 100644 index 000000000..2b913f964 --- /dev/null +++ b/docs/running-zingg/compare-model-results.md @@ -0,0 +1,132 @@ +--- +description: >- + Compare the accuracy of two trained Zingg models before deploying to + production. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# Compare Model Results + +{% hint style="info" icon="right-long" %} +**Enterprise** **Plus** only. Comparing model results is available in Zingg Enterprise Plus (ZinggES).\ +Not available in Community, Enterprise Lite, or Enterprise. +{% endhint %} + +When you build a new model or update an existing one, you need to know whether the new model actually performs better than the old one before switching production workloads. Comparing model results lets you run both models on the same dataset and evaluate their outputs side by side. + +This is part of the Zingg model workflow: build → tweak → run → interpret → compare → save. Comparing model results is the step between interpreting and saving - it gives you confidence that the model you are saving is better than the one it is replacing. + +### When to use Compare model results + +Use this when you have: + +Retrained an existing model with more labeled data and want to confirm the updated model outperforms the original. + +* Changed field match types; for example, switching a field from `FUZZY` to `EXACT` or adding a new field and want to validate the impact before promoting the change. +* Run an A/B test between two modeling strategies and need a quantitative basis for choosing one over the other. +* Changed blocking strategy settings and want to measure the effect on cluster quality before committing to production + +{% hint style="success" icon="right-long" %} +**Read more:** + +* Model workflow context - [Run the match phase](run-the-match-phase.md) +* Reassign Zingg ID when promoting a new model - [Reassign Zingg ID](reassign-zingg-id.md) +{% endhint %} + +#### How Compare Model Results works + +Compare model results uses the `diff` phase to analyze both model outputs and identify exactly which records and clusters were affected by your changes. + +The `diff` phase compares two configurations: + +* **Original configuration:** your baseline model (e.g., with `FUZZY` matching) +* **New configuration:** your updated model (e.g., with `EXACT` matching or new deterministic rules) + +The `diff` phase then identifies: + +* Records that moved between clusters due to model changes +* New clusters created in the updated model +* Merged or split clusters between the two models + +{% tabs %} +{% tab title="Enterprise" %} +### Python + +```python +from zingg.client import* +from zingg.pipes import* +from zinggEC.enterprise.common.EArguments import* +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition +from zinggES.enterprise.spark.ESparkClient import* +from zinggEC.enterprise.common.TransformedOutputArguments import* +from zinggEC.enterprise.common.EClientOptions import* +``` + +#### Step 1: Define ORIGINAL baseline config + +```python +originalArgs = EArguments() +#... your original field definitions... +originalArgs.setModelId("100") +originalArgs.setZinggDir("./models") +``` + +#### Step 2: Define NEW/UPDATED Config + +```python +newArgs = EArguments() +#... your updated field definitions... +newArgs.setModelId("200") +newArgs.setZinggDir("./models") +``` + +#### Step 3: Create TransformedOutputArguments + +```python +diffArgs = TransformedOutputArguments() +diffArgs.setParentArgs(newArgs) +diffArgs.setOriginalArgs(originalArgs) + +diffOutputPipe = ECsvPipe("diffOutput", "/tmp/zinggDiff") +diffOutputPipe.setHeader("true") +diffArgs.setTransformedOutputPath(diffOutputPipe) +``` + +#### Step 4: Execute diff + +```python +diffOptions = EClientOptions([ + EClientOptions.PHASE, + "diff" +]) +zinggDiff = EZingg(diffArgs, diffOptions) +zinggDiff.initAndExecute() +``` + +### CLI + +```bash +./scripts/zingg.sh \ + --phase diff \ + --conf examples/febrl/sparkIncremental/configdiff.json \ + --compareTo examples/febrl/configBaseline.json \ + --properties-file config/zingg.conf +``` +{% endtab %} + +{% tab title="Enterprise for Snowfalke" %} +_**CHECK WITH SONAL - Enterprise Snowflake content for this topic to be provivded by Sonal**_ +{% endtab %} +{% endtabs %} + +### Understand why use Diff with use cases + +* **Field type changes:** When you switch a field from `FUZZY` to `EXACT` or add a new field, run `diff` before deploying. See exactly which clusters changed and decide whether the new model behaves better for your data. +* **Validating model improvements:** After retraining with more labeled data, use `diff` to confirm the updated model outperforms the original. Don't assume more labels always improve results—verify. +* **A/B testing modeling strategies:** Run two different configurations on the same dataset and compare their outputs with `diff`. Choose the strategy with the better cluster quality based on evidence, not intuition. +* **Blocking strategy changes:** Changed `setBlockingModel()` from `DEFAULT` to `WIDER`? Run `diff` to measure the effect on cluster quality before committing to production. +* **Regression detection:** Ensure that changes made to improve one part of the model have not degraded match quality on other parts of your data. +* **Cluster evolution tracking:** Understand how clusters merge, split, or change as your model evolves. Especially useful when your input data has changed significantly between model versions. diff --git a/docs/running-zingg/configure-zingg.md b/docs/running-zingg/configure-zingg.md new file mode 100644 index 000000000..465d3cf97 --- /dev/null +++ b/docs/running-zingg/configure-zingg.md @@ -0,0 +1,487 @@ +--- +description: >- + Set up your field definitions, pipes, and arguments - the foundation every + subsequent Zingg phase depends on. +--- + +# Configure Zingg + +Configuring Zingg is the first step in every Zingg workflow. This page follows notebook 01 step by step; defining your fields, configuring your pipes, and creating the arguments object that all subsequent phases inherit. + +Complete this page before running any other phase. Every phase reads the configuration set here: + +* `findTrainingData` → \[[Create Training Data](create-training-data.md)] +* `label` → \[[Label Training Pairs](label-training-pairs.md)] +* `train` → \[[Build and Save the Model](build-and-save-the-model.md)] +* `match` → \[[Run the Match Phase](run-the-match-phase.md)] +* `link` → \[[Link Across Datasets](link-across-datasets.md)] +* `runIncremental` → \[[Run Incremental Matching](run-incremental-matching.md)] + +{% hint style="success" icon="right-long" %} +Full parameter reference with all config options and schema → [Configuration Schema](../reference/configuration-schema.md) +{% endhint %} + +{% tabs %} +{% tab title="Community" %} +### Step 1: Verify installation + +```python +from zingg.client import * +from zingg.pipes import * +``` + +Verify in your notebook + +```python +!pip show zingg +``` + +### Step 2: Build the arguments object + +#### Python + +```python +args = Arguments() +args.setModelId("100") +args.setZinggDir("models") +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.5) +``` + +{% hint style="success" icon="right-long" %} +`setModelId` - unique name for this model. + +`setZinggDir` - where Zingg writes model files and training data. + +Use the same `modelId` in all subsequent phases for this run. +{% endhint %} + +#### JSON + +```json +{ + "modelId" : "100", + "zinggDir" : "models", + "numPartitions" : 4, + "labelDataSampleSize" : 0.5 +} +``` + +#### Understanding `numPartitions` and `labelDataSampleSize` + +**`numPartitions`** - The number of Spark partitions over which input data is distributed. Set this to approximately 20–30 times the number of worker cores. This is the most important configuration for performance. + +**`labelDataSampleSize`** - Fraction of the data scanned to find candidate pairs. Adjust between `0.0001` and `0.1`. If `findTrainingData` is slow, reduce to `0.05` or lower. If it is too small, Zingg may not find the right edge cases. + +### Step 3: Define fields and match types + +#### Python + +```python +fname = FieldDefinition("fname", "string", MatchType.FUZZY) +lname = FieldDefinition("lname", "string", MatchType.FUZZY) +stNo = FieldDefinition("stNo", "string", MatchType.FUZZY) +add1 = FieldDefinition("add1", "string", MatchType.FUZZY) +add2 = FieldDefinition("add2", "string", MatchType.FUZZY) +city = FieldDefinition("city", "string", MatchType.FUZZY) +areacode = FieldDefinition("areacode", "string", MatchType.FUZZY) +state = FieldDefinition("state", "string", MatchType.FUZZY) +dob = FieldDefinition("dob", "string", MatchType.FUZZY) +ssn = FieldDefinition("ssn", "string", MatchType.FUZZY) + +fieldDefs = [ + fname, lname, stNo, add1, add2, city, + areacode, state, dob, ssn +] +args.setFieldDefinition(fieldDefs) +``` + +#### JSON + +```json +{ + "fieldDefinition" : [ + { + "fieldName" : "fname", + "matchType" : "FUZZY", + "fields" : "fname", + "dataType" : "string" + }, + { + "fieldName" : "lname", + "matchType" : "FUZZY", + "fields" : "lname", + "dataType" : "string" + }, + { + "fieldName" : "stNo", + "matchType" : "FUZZY", + "fields" : "stNo", + "dataType" : "string" + }, + { + "fieldName" : "add1", + "matchType" : "FUZZY", + "fields" : "add1", + "dataType" : "string" + }, + { + "fieldName" : "add2", + "matchType" : "FUZZY", + "fields" : "add2", + "dataType" : "string" + }, + { + "fieldName" : "city", + "matchType" : "FUZZY", + "fields" : "city", + "dataType" : "string" + }, + { + "fieldName" : "areacode", + "matchType" : "FUZZY", + "fields" : "areacode", + "dataType" : "string" + }, + { + "fieldName" : "state", + "matchType" : "FUZZY", + "fields" : "state", + "dataType" : "string" + }, + { + "fieldName" : "dob", + "matchType" : "FUZZY", + "fields" : "dob", + "dataType" : "string" + }, + { + "fieldName" : "ssn", + "matchType" : "FUZZY", + "fields" : "ssn", + "dataType" : "string" + } + ] +} +``` + +{% hint style="success" icon="right-long" %} +**Read more**: Match types reference - [Match types](../zingg-concepts/how-zingg-learns/match-types/) | C[onfiguration schema](../reference/configuration-schema.md) +{% endhint %} + +### Step 4: Configure input and output pipes + +#### Python + +```python +schema = "id string, fname string, \ +lname string, stNo string, add1 string, \ +add2 string, city string, areacode string,\ + state string, dob string, ssn string" + +inputPipe = CsvPipe("testFebrl", "examples/febrl/test.csv", schema) +args.setData(inputPipe) + +outputPipe = CsvPipe("resultFebrl", "/tmp/febrlOutput") +args.setOutput(outputPipe) +``` + +#### JSON + +```json +{ + "data" : [ { + "name" : "testFebrl", + "format" : "csv", + "props" : { + "path" : "examples/febrl/test.csv", + "delimiter" : ",", + "header" : "true" + }, + "schema" : "id string, fname string, lname string, stNo string, add1 string, add2 string, city string, areacode string, state string, dob string, ssn string" + } ], + "output" : [ { + "name" : "resultFebrl", + "format" : "csv", + "props" : { + "path" : "/tmp/febrlOutput", + "delimiter" : ",", + "header" : "true" + } + } ] +} +``` + +#### Configuring through environment variables + +If you do not want to pass sensitive values such as passwords through the config file, configure them through system environment variables. Wrap the variable name in dollar signs in your config: + +* Strings: `"$var$"` (with quotes) +* Booleans and numerics: `$var$` (without quotes) + +```json +{ + "output" : [ { + "name" : "unifiedCustomers", + "format" : "net.snowflake.spark.snowflake", + "props" : {"path" : "$location$", "password" : "$passwd$"} + } ], + "labelDataSampleSize" : 0.5, + "numPartitions" : 4, + "modelId" : "$modelId$", + "zinggDir" : "models", + "collectMetrics" : "$collectMetrics$" +} +``` + +{% hint style="success" icon="right-long" %} +**Read more**: For all supported connector types and formats - [Connect Your Data](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/connect-your-data) +{% endhint %} +{% endtab %} + +{% tab title="Enterprise" %} +### Step 1: Verify installation and imports + +```python +from zingg.client import * +from zingg.pipes import * +from zinggEC.enterprise.common.epipes import * +from zinggEC.enterprise.common.EArguments import * +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition +``` + +Verify the `zinggEC` package is installed by running `!pip show zinggEC` in a separate cell. + +### Step 2: Build the Enterprise arguments object + +```python +args = EArguments() +args.setModelId("100") +args.setZinggDir("/tmp/models") +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.5) +``` + +Set the blocking strategy directly after `setLabelDataSampleSize`. If not set, the model follows `DEFAULT`. + +#### Python + +```python +args.setBlockingModel("DEFAULT") +``` + +#### JSON + +```json +{ + "modelId": "100", + "zinggDir": "/tmp/models", + "numPartitions": 4, + "labelDataSampleSize": 0.5 +} +``` + +### Step 3: Define fields with `EFieldDefinition` + +#### Python + +```python +recId = EFieldDefinition( + "recId", + "string", + MatchType.DONT_USE +) +recId.setPrimaryKey(True) + +fname = EFieldDefinition( + "fname", + "string", + MatchType.FUZZY +) +lname = EFieldDefinition( + "lname", + "string", + MatchType.FUZZY +) +stNo = EFieldDefinition( + "stNo", + "string", + MatchType.FUZZY +) +add1 = EFieldDefinition( + "add1", + "string", + MatchType.FUZZY +) +add2 = EFieldDefinition( + "add2", + "string", + MatchType.FUZZY +) +city = EFieldDefinition( + "city", + "string", + MatchType.FUZZY +) +areacode = EFieldDefinition( + "areacode", + "string", + MatchType.FUZZY +) +state = EFieldDefinition( + "state", + "string", + MatchType.FUZZY +) +dob = EFieldDefinition( + "dob", + "string", + MatchType.FUZZY +) +ssn = EFieldDefinition( + "ssn", + "string", + MatchType.FUZZY +) + +fieldDefs = [ + recId, + fname, + lname, + stNo, + add1, + add2, + city, + areacode, + state, + dob, + ssn +] +args.setFieldDefinition(fieldDefs) +``` + +{% hint style="info" icon="right-long" %} +Enterprise requires a primary key field for `runIncremental`. Mark the primary key field by calling `recId.setPrimaryKey(True)` if you plan to use incremental matching. +{% endhint %} + +### Step 4: Configure input and output pipes + +#### Python + +```python +schema = ("recId string, fname string, " + "lname string, stNo string, " + "add1 string, add2 string, " + "city string, areacode string, " + "state string, dob string, " + "ssn string") + +inputPipe = ECsvPipe( + "testFebrl", + "examples/febrl/test.csv", + schema +) +args.setData(inputPipe) + +outputPipe = ECsvPipe( + "resultFebrl", + "/tmp/febrlOutput" +) +outputPipe.setHeader("true") +args.setOutput(outputPipe) +``` + +### Step 5: Deterministic matching (optional) + +{% hint style="info" icon="right-long" %} +Deterministic matching - **Enterprise** only. + +Skip this step if you only need probabilistic matching. +{% endhint %} + +#### Python + +```python +detMatchNameAdd = DeterministicMatching( + 'fname', + 'stNo', + 'add1' +) +detMatchNameDobSsn = DeterministicMatching( + 'fname', + 'dob', + 'ssn' +) +detMatchNameEmail = DeterministicMatching( + 'fname', + 'email' +) +args.setDeterministicMatchingCondition( + detMatchNameAdd, + detMatchNameDobSsn, + detMatchNameEmail +) +``` + +#### JSON + +```json +{ + "deterministicMatching": [ + {"matchCondition": [ + {"fieldName": "fname"}, + {"fieldName": "stNo"}, + {"fieldName": "add1"} + ]}, + {"matchCondition": [ + {"fieldName": "fname"}, + {"fieldName": "dob"}, + {"fieldName": "ssn"} + ]}, + {"matchCondition": [ + {"fieldName": "fname"}, + {"fieldName": "email"} + ]} + ] +} +``` + +### Step 6: Pass Through (optional) + +{% hint style="info" icon="right-long" %} +Pass Through - **Enterprise** only. + +Excludes specific records from matching while still including them in output with their own `Zingg ID`. +{% endhint %} + +Pass Through excludes specific records from matching while still including them in output with their own Zingg ID. Records matching the passthrough expression appear in the identity graph but never influence cluster formation. + +**Note**: Zingg internally applies the negation of `passthroughExpr` to filter matching records. If the passthrough condition applies to nullable fields, ensure the negative of the expression yields the records that are `NOT` passthrough. + +#### Python + +```python +args.setPassthroughExpr("fname = 'matilda'") +``` + +#### JSON + +```json +{ + "passthroughExpr": "fname = 'matilda'" +} +``` + +#### Example with null-safe expression + +```json +{ + "passthroughExpr": "is_deceased = true AND is_deceased is NOT NULL" +} +``` +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +**CHECK WITH SONAL ABOUT THIS TOPIC - NEEDS ENTIRELY DIFFERENT SET OF CONTENT TO BE DISCUSSED LATER.** +{% endtab %} +{% endtabs %} diff --git a/docs/running-zingg/create-training-data.md b/docs/running-zingg/create-training-data.md new file mode 100644 index 000000000..8589e9f9a --- /dev/null +++ b/docs/running-zingg/create-training-data.md @@ -0,0 +1,108 @@ +--- +description: >- + Run findTrainingData to generate candidate pairs from your data for labeling. + The first active step in building your Zingg model. +--- + +# Create Training Data + +The `findTrainingData` phase prompts Zingg to search for edge cases in your data-record pairs that are informative for learning. Zingg selects these judiciously so that your labeling effort is minimized and models can be built quickly. + +Run `findTrainingData` first, then run the `label` phase. Repeat this cycle until you have enough labeled pairs. + +{% hint style="success" icon="right-long" %} +Zingg selects the most informative pairs from your data not random samples. Label until all field types and data variation patterns in your schema are represented. If accuracy needs improvement after the first match run, return to labeling and focus on patterns that are missing or underrepresented. +{% endhint %} + +{% tabs %} +{% tab title="Community" %} +### Set label data sample size + +```python +args.setLabelDataSampleSize(0.5) +``` + +### Run findTrainingData + +```bash +./zingg.sh --phase findTrainingData --conf config.json +``` + +#### Using Python API + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "findTrainingData" +]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### Using pre-existing training data + +If you already have labeled pairs from a previous run or external source, you can supply them to Zingg using the `trainingSamples` attribute in your config. + +Your training data must include: + +* `z_cluster` - groups records together; uniquely identifies the group +* `z_isMatch` - `1` if the records match, `0` if they do not. Must be the same value for all records in the same `z_cluster` group. + +#### Using JSON config + +```json +{ + "trainingSamples": [ + { + "name": "existingTraining", + "format": "csv", + "props": { + "path": "/path/to/training.csv", + "delimiter": ",", + "header": "true" + } + } + ] +} +``` + +{% hint style="success" icon="right-long" %} +Even when supplying pre-existing training data, it is advisable to run `findTrainingData` and `label` for a few rounds to tune Zingg with the patterns it needs to learn independently. +{% endhint %} +{% endtab %} + +{% tab title="Enterprise" %} +### Set label data sample size + +```python +args.setLabelDataSampleSize(0.5) +``` + +#### Python + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "findTrainingData" +]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### CLI + +```bash +./zingg.sh --phase findTrainingData --conf config.json +``` + +Enterprise also provides the `findAndLabel` combined phase, which runs `findTrainingData` and `label` together in a single call. Use this for smaller datasets where `findTrainingData` completes quickly: + +{% hint style="info" icon="right-long" %} +Candidate pairs are written to `zinggDir/modelId`. Run the label phase next to review and label these pairs. +{% endhint %} +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +**CHECK WITH SONAL ABOUT THIS TOPIC - NEEDS ENTIRELY DIFFERENT SET OF CONTENT TO BE DISCUSSED LATER.** +{% endtab %} +{% endtabs %} diff --git a/docs/running-zingg/experience-zingg.md b/docs/running-zingg/experience-zingg.md new file mode 100644 index 000000000..e8467f1bc --- /dev/null +++ b/docs/running-zingg/experience-zingg.md @@ -0,0 +1,115 @@ +--- +description: >- + See Zingg resolve duplicate records in under 5 minutes no setup, no + configuration, no data preparation needed. +--- + +# Experience Zingg + +{% hint style="success" icon="right-long" %} +**Prerequisites:** Docker installed on your machine. Nothing else required. +{% endhint %} + +_**CHECK WITH SONAL - IF WE STILL NEED THIS PAGE AS PER THE NEW GROUPING - AS WE HAVE A DEDICATED PAGE FOR DOCKER - QUICK START. OUR INITIAL IDEA ABOUT KEEPING THIS PAGE IS NOW CHANGED.**_ + +The fastest way to understand what Zingg does is to see it work. This page uses Docker and\ +a pre-trained model bundled with Zingg to run entity resolution on a sample dataset in three\ +commands. There is no installation, no configuration, and no labeling required. + +You will see Zingg take records that look different variations in name, address, and\ +date of birth, and resolve them into clusters representing the same real-world person. That\ +is entity resolution. + +### Run Zingg in 3 commands + +#### Step 1: Pull the Zingg Docker image + +```bash +docker pull zingg/zingg:0.5.0 +``` + +#### Step 2: Start the container + +```bash +docker run -it zingg/zingg:0.5.0 bash +``` + +If you see a permission error, use: + +```bash +docker run -v /tmp:/tmp -it zingg/zingg:0.5.0 bash +``` + +#### Step 3: Run match using the bundled FEBRL sample data and pre-trained model + +```bash +./scripts/zingg.sh --phase match --conf examples/febrl/config.json +``` + +{% hint style="success" icon="right-long" %} +### **What just happened** + +Zingg read the `FEBRL` sample dataset bundled in the Docker image, applied a pre-trained entity resolution model to it, and wrote the resolved output to the output path configured in `examples/febrl/config.json`. + +No training was needed because the model was pre-built for this dataset. +{% endhint %} + +### Reading the output + +The output contains all your input fields plus three columns added by Zingg: + +* `Z_CLUSTER` - a unique identifier shared by all records Zingg resolved as the same\ + entity. Records with the same `Z_CLUSTER` value represent the same real-world person\ + in the `FEBRL` dataset. +* `Z_MINSCORE` - the lowest similarity score between any two records in that cluster.\ + Indicates how confidently the weakest link in the cluster was matched. +* `Z_MAXSCORE` - the highest similarity score between any two records in that cluster.\ + Indicates the strongest match within the cluster. + +{% hint style="success" icon="right-long" %} +**Read more**: To know more about output scores, check [Interpret Output Score](../interpreting-results/interpret-output-scores.md). +{% endhint %} + +### VIDEO TO BE ADDED + +**Paste the Zingg video URL here once available from the team.** + +
+ +What is the FEBRL dataset? + +FEBRL (Freely Extensible Biomedical Record Linkage) is a synthetic dataset generator\ +that produces realistic person records with deliberate variations. The records contain\ +fields for first name, last name, street number, address lines, city, state, date of birth, and a national identifier. + +The variations are intentional - the same person appears multiple times with different\ +spellings, missing fields, and formatting differences across records. This makes FEBRL\ +ideal for demonstrating entity resolution because the correct answer is known: Zingg's\ +output can be verified against the ground truth built into the dataset. + +Zingg ships with a pre-trained model for FEBRL under the models folder in the release. This is why Experience Zingg requires no training step - the model is already built. + +
+ +
+ +What happens after this? + +Experience Zingg uses a pre-trained model on a sample dataset. In a real implementation, you would: + +1. Connect your own data using Zingg pipes. +2. Define the fields you want to match on and choose a match type for each. +3. Run `findTrainingData` to generate candidate pairs from your data. +4. Label those pairs as "Match" or "No Match" to teach Zingg what a match looks like in your domain. +5. Train the model on your labeled pairs. +6. Run `match` or `link` on your full dataset. + +The Quick Start page walks you through this full workflow on your own platform using sample data provided by the Zingg team. + +As long as your input columns and field types are not changing, the same model works - you do not need to retrain. If you change a match type, you can add more labeled pairs on top of existing training data. + +
+ +{% hint style="warning" icon="right-long" %} +Enterprise adds persistent identity with Zingg ID, incremental matching, and production-grade notebooks. [Talk to us about Enterprise](https://www.zingg.ai/company/contact/contact) +{% endhint %} diff --git a/docs/running-zingg/generate-model-documentation.md b/docs/running-zingg/generate-model-documentation.md new file mode 100644 index 000000000..a994dc5c6 --- /dev/null +++ b/docs/running-zingg/generate-model-documentation.md @@ -0,0 +1,73 @@ +--- +description: >- + Generate readable documentation of your labeled training data to inspect + quality before committing to the train phase. +--- + +# Generate Model Documentation + +{% hint style="success" icon="right-long" %} +Run this BETWEEN `label` and `train`. Do not skip - it lets you inspect training data quality before the model is built. +{% endhint %} + +The `generateDocs` phase produces readable documentation of the records you have marked during the label phase, including both matches and non-matches. Use this to review training data quality, understand what Zingg has learned, and share results with subject matter experts before training begins. + +The documentation is written to the `zinggDir/modelId` folder and can be viewed in a browser. + +### Run generateDocs + +{% tabs %} +{% tab title="Community" %} +### **Python** + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "generateDocs" +]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase generateDocs --conf \ + --showConcise=true +``` +{% endtab %} + +{% tab title="Enterprise" %} +### **Python** + +{% hint style="info" icon="right-long" %} +Enterprise uses `EZingg` instead of `Zingg`. +{% endhint %} + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "generateDocs" +]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +### CLI + +```bash +./scripts/zingg.sh --phase generateDocs --conf \ + --showConcise=true +``` +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +**CHECK WITH SONAL ABOUT THIS TOPIC - NEEDS ENTIRELY DIFFERENT SET OF CONTENT TO BE DISCUSSED LATER.** +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +The generated documentation is viewable in a browser. It shows all pairs you labelled as matches and non-matches in a readable format. Share this with subject matter experts to validate training data quality before running `train`. + +[Build and Save the Model ](build-and-save-the-model.md)→ run `train` once you are satisfied with the training data quality. +{% endhint %} diff --git a/docs/running-zingg/install-zingg.md b/docs/running-zingg/install-zingg.md new file mode 100644 index 000000000..862fb69f7 --- /dev/null +++ b/docs/running-zingg/install-zingg.md @@ -0,0 +1,342 @@ +--- +description: >- + Install Zingg on your platform - Community and Enterprise editions across + Spark and notebook environments +--- + +# Install Zingg + +Zingg runs on Spark (all editions) or Snowflake (Enterprise only). Select your platform from the tabs below. Each tab covers both Community and Enterprise where applicable. + +### Prerequisites + +The following prerequisites apply to local and self-managed Spark installations only. Managed Spark services such as Databricks, Fabric, EMR, and Synapse handle the Spark runtime for you - you only need to install the Zingg library on those platforms. + +* Java: JDK version 11.0.23 or compatible +* Spark: version 3.5.0 or compatible + +{% tabs %} +{% tab title="Azure Databricks" %} +### **Step 1: Install Zingg on your Databricks cluster** + +Go to **Compute → your cluster → Libraries → Install new → PyPI**. + +Install the Zingg Python package matching your edition. + +#### **Community** + +```bash +%pip install zingg +``` + +#### Enterprise Lite or Enterprise + +```bash +%pip install zinggEC +``` + +#### Enterprise Plus + +```bash +%pip install zinggES +``` + +Install the required `tabulate` dependency (all editions): + +```bash +%pip install tabulate +``` + +Restart the Python kernel: **Runtime → Restart Python**. + +#### **Verify the installation** + +```bash +%pip show zingg +``` +{% endtab %} + +{% tab title="Microsoft Fabric" %} +Zingg on Microsoft Fabric uses the Fabric notebook interface and a custom environment.\ +Follow these steps before opening the Zingg notebook. + +### **Step 1: Create a Fabric workspace and session** + +1. Sign in to Microsoft Fabric and create a new workspace. +2. Name it something like `Zingg-Fabric` . +3. When prompted for a session cluster, choose **New Standard Session.** + +### **Step 2: Download the Zingg example notebook** + +1. Download `ExampleNotebook.ipynb` from `github.com/zinggAI/zingg/blob/main/examples/fabric/ExampleNotebook.ipynb` . +2. Upload the notebook to your Fabric workspace. + +### **Step 3: Create a new environment and install the Zingg JAR** + +1. Go to the **Environment** tab in your workspace and click **New Environment**. Name it `Zingg Environment`. +2. Download the latest Zingg release `tar` file from `github.com/zinggAI/zingg/releases` . +3. Extract the `tar` file and locate the Zingg JAR file inside it. +4. In your `Zingg Environment`, go to **Custom Library** and upload the JAR file. +5. **Save** and **Publish** the Environment. + +### **Step 4: Install the Zingg Python package** + +Install the Zingg Python package matching your edition. + +#### **Community:** + +```bash +pip install zingg +``` + +#### Enterprise Lite or Enterprise: + +```bash +pip install zinggEC +``` + +#### Enterprise Plus: + +```bash +pip install zinggES +``` + +Verify the installation: + +```bash +pip show zingg +``` + +### **Step 5: Create a Lakehouse and upload your data** + +1. In your workspace, click **New Item** and select **Lakehouse**. Name it and go inside it. +2. Click **Get Data** and upload your data file as CSV or Parquet. + +{% hint style="success" icon="right-long" %} +Note the `abfss` path of your Lakehouse for use in the notebook. It follows this format: `abfss://@onelake.dfs.fabric.microsoft.com//Files` + +For the full step-by-step Fabric guide including screenshots → [Run on Microsoft Fabric](../platform-guides/platform-guide-for-microsoft-fabric.md) +{% endhint %} +{% endtab %} + +{% tab title="AWS EMR" %} +Two options for running Zingg on AWS EMR. + +### Option A - spark-submit with Zingg JAR + +Use the `spark-submit` option passing the Zingg JAR, phase name, and config file. `config.json` must be available locally on the driver. + +{% code title="spark-submit example" expandable="true" %} +```bash +aws emr create-cluster \ + --name "Add Spark Step Cluster" \ + --release-label emr-6.2.0 \ + --applications Name=Zingg \ + --ec2-attributes KeyName=myKey \ + --instance-type \ + --instance-count \ + --steps Type=Spark,Name="Zingg",ActionOnFailure=CONTINUE,Args=[--class,zingg.spark.client.SparkClient,,--phase,,--conf,] \ + --use-default-roles +``` +{% endcode %} + +### Option B - AWS EMR Notebooks + +Run Zingg Python code directly in AWS EMR Notebooks using the Python API. Install the Zingg Python package matching your edition. + +#### Community + +```bash +%pip install zingg +``` + +#### Enterprise Lite or Enterprise + +```bash +%pip install zinggEC +``` + +#### Enterprise Plus + +```bash +%pip install zinggES +``` + +#### **Verify the installation** + +```bash +%pip show zingg +``` +{% endtab %} + +{% tab title="AWS Glue" %} +**AWS Glue install steps are not documented on any live docs page. CHECK WITH SONAL before publishing this tab.** +{% endtab %} + +{% tab title="GCP Dataproc" %} +_**CHECK WITH SONAL - - NEED TEAMS HELP TO CHECK WHAT EXACTLY FROM THE GCS GUIDE TO BE ADDED HERE**_ +{% endtab %} + +{% tab title="Azure Synapse" %} +**CHECK WITH SONAL - if "Zingg on Azure Synapse" is the same as "Databricks installation instructions" above. Are the install method and library setup are the same.** +{% endtab %} + +{% tab title="Local Spark" %} +{% hint style="success" icon="right-long" %} +Docker is the fastest way to get started locally. Use installing from release if you need a specific Spark version or want to integrate with an existing Spark installation. +{% endhint %} + +### Option A - Docker (recommended) + +```bash +docker pull zingg/zingg:0.5.0 +docker run -it zingg/zingg:0.5.0 bash +``` + +If permission denied: + +```bash +docker run -v /tmp:/tmp -it zingg/zingg:0.5.0 bash +``` + +### Option B - Installing from Release + +Download the latest release from GitHub: `github.com/zinggAI/zingg/releases` + +**Example for Zingg 0.5.0 on Spark 3.5.0** + +Assumes Zingg 0.5.0 on Spark 3.5.0 + +**Prerequisites**: Java JDK 11.0.23, Spark 3.5.0 + +```bash +wget https://github.com/zinggAI/zingg/releases/download/v0.5.0/zingg-0.5.0-spark_3.5.tar.gz + +tar -xvf zingg-0.5.0-spark_3.5.tar.gz +``` + +### Set up environment variables + +Add the following to `~/.bash_aliases` (Linux) or `~/.zshrc` (macOS): + +```bash +export JAVA_HOME= +export SPARK_HOME= +export SPARK_MASTER=local[*] +export ZINGG_HOME= +export PATH=$PATH:$JAVA_HOME/bin:$SPARK_HOME/bin:$SPARK_HOME/sbin:$ZINGG_HOME/scripts +``` + +Also verify that your machine's IP is added to `/etc/hosts` for localhost. Run `ifconfig` to find the IP and add it. + +#### Verify your installation + +Run bash and print the aliases to confirm they are set correctly: + +```bash +echo $SPARK_HOME +echo $JAVA_HOME +java --version +echo $ZINGG_HOME +``` + +Then run a sample program to confirm the installation works: + +```bash +cd zingg +./scripts/zingg.sh --phase trainMatch --conf examples/febrl/config.json +``` + +This builds Zingg models and finds duplicates in `examples/febrl/test.csv`. You will see Zingg logs on the console and output files under `/tmp/zinggOutput` with matching records sharing the same cluster ID. If you see this, Zingg is correctly installed. +{% endtab %} + +{% tab title="Snowflake (ENT)" %} +{% hint style="info" icon="right-long" %} +Enterprise only. Zingg on Snowflake uses the Enterprise Snowflake package and runs natively inside Snowflake using Snowpark — no Spark cluster required. +{% endhint %} + +**CHECK WITH SONAL** - Snowflake Enterprise install is referenced at\ +docs.zingg.ai/latest/stepbystep/installation/installing-zingg-enterprise-snowflake + +### **Prerequisites** + +* Java JDK 11 +* A Snowflake account with `ZINGG_STAGE` already created +* A Zingg Enterprise licence + +### **Install Zingg Enterprise for Snowflake** + +Extract the Enterprise Snowflake tar file and configure environment variables. + +{% code title="Decompress archive" overflow="wrap" %} +```bash +gzip -d zingg-enterprise-snowflake-.tar.gz +``` +{% endcode %} + +{% code title="Extract archive" overflow="wrap" %} +```bash +tar xvf zingg-enterprise-snowflake-.tar +``` +{% endcode %} + +Set the environment variables: + +{% code title="Set environment variables" overflow="wrap" %} +```bash +export ZINGG_SNOW_HOME=~/zingg-enterprise-snowflake- +export ZINGG_SNOW_JAR=~/zingg-enterprise-snowflake- +``` +{% endcode %} + +Move your licence file into the install directory: + +{% code title="Move licence file" overflow="wrap" %} +```bash +mv ~/zingg.license . +``` +{% endcode %} + +{% hint style="info" icon="right-long" %} +Add `ZINGG_SNOW_JAR` and `ZINGG_SNOW_HOME` to `.bashrc` so they persist across sessions. For the full end-to-end Snowflake guide → [Run on Snowflake](../platform-guides/platform-guide-for-snowflake.md) +{% endhint %} + +### **Create the Snowflake properties file (`snowEnv.txt`)** + +{% code title="Create snowEnv.txt" overflow="wrap" %} +```bash +touch snowEnv.txt +``` +{% endcode %} + +**Contents of `snowEnv.txt`** + +{% code title="snowEnv.txt" overflow="wrap" %} +```bash +URL={snowflake_url} +USER={snowflake_user_name} +PASSWORD={snowflake_password} +ROLE={role} +WAREHOUSE={warehouse} +DB={database_name} +SCHEMA={schema} +CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY=900 +``` +{% endcode %} + +`CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY` is the number of seconds between client attempts to update the session token. Valid range: 900 to 3600. + +### **Verify the installation** + +{% code title="Verify installation" overflow="wrap" expandable="true" %} +```bash +./scripts/zingg.sh \ + --properties-file snowEnv.txt \ + --phase findTrainingData \ + --conf examples/febrl/configSnow.json +``` +{% endcode %} + +This will run Zingg models and produce tables named `UNIFIED_CUSTOMERS_MODELID` with matching records sharing the same cluster ID. Congratulations, Zingg Enterprise for Snowflake is installed. +{% endtab %} +{% endtabs %} diff --git a/docs/running-zingg/knowledge-graph.md b/docs/running-zingg/knowledge-graph.md new file mode 100644 index 000000000..3d913e116 --- /dev/null +++ b/docs/running-zingg/knowledge-graph.md @@ -0,0 +1,46 @@ +--- +description: >- + Build a knowledge graph on top of your resolved entities to analyze + relationships across your data. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# Knowledge Graph + +{% hint style="info" icon="right-long" %} +Enterprise Plus only. Knowledge Graph is available in Zingg Enterprise Plus (ZinggES). Not available in Community, Enterprise Lite, or Enterprise. +{% endhint %} + +Entity resolution resolves who your entities are. The Knowledge Graph goes one step further - it maps the relationships between those entities once they are resolved. + +After Zingg groups records into clusters and assigns Zingg IDs, the knowledge graph layer builds a graph structure on top of those resolved entities. This lets you query not just, "Is this the same customer?" but also "how are these customers connected to each other through shared addresses, accounts, devices, or transactions?" + +Common applications include fraud detection (finding rings of connected entities), customer householding (understanding which individuals belong to the same household), supplier network analysis, and compliance screening (identifying indirect relationships between\ +sanctioned entities and your counterparties). + +### How it fits in the Zingg workflow + +The Knowledge Graph is a post-match feature. The sequence is: + +1. Run a match or incremental to resolve entities and assign Zingg IDs. +2. Zingg IDs become the nodes in the knowledge graph. +3. The knowledge graph layer maps edges between nodes based on shared attributes, transactions, or relationships defined by your data model. +4. Query the graph to find connected entities, paths between nodes, and clusters of related records. + +_**CHECK WITH TEAM/SONAL—Knowledge Graph is confirmed as an Enterprise Plus feature on the compare-versions page but is not yet documented on any live docs page. Please provide:**_ + +1. _**How the Knowledge Graph is configured and invoked**_ +2. _**The graph database or format used (Neo4j, native graph store?)**_ +3. _**A worked example showing node and edge definition**_ +4. _**How to query the graph after entity resolution runs**_ + +_**Full content and code example will be added once the above is confirmed**_ + +{% hint style="success" icon="right-long" %} +**Read more:** + +* Entity resolution and graph analysis with Neo4j - [Connect Graph Databases](../connect-your-data/connect-graph-databases-neo4j.md) +{% endhint %} diff --git a/docs/running-zingg/label-training-pairs.md b/docs/running-zingg/label-training-pairs.md new file mode 100644 index 000000000..8b57ebe44 --- /dev/null +++ b/docs/running-zingg/label-training-pairs.md @@ -0,0 +1,108 @@ +--- +description: >- + Review candidate pairs and label each as Match, No Match, or Can't Say. The + human feedback step that teaches Zingg what a match looks like in your data. +--- + +# Label Training Pairs + +The `label` phase opens an interactive layer where you review the candidate pairs found by `findTrainingData` and mark each pair. This is the only step in the Zingg workflow that requires human input. No ML knowledge is needed, just your domain understanding of whether two records represent the same real-world entity. + +If you already have labeled data from an external source, you can supply it directly using `trainingSamples` in your configuration. See [Create Training Data](create-training-data.md) for how to set this up. + +30 to 40 matching pairs is a strong starting point. Label until you feel that your labeled examples represent all field types and data patterns in your schema. If accuracy needs improvement after your first match run, return to labeling—focus on the patterns or field combinations that appear to be missing or underrepresented. + +{% stepper %} +{% step %} +### Step 1: Label + +Review each pair presented. Enter your decision: Match (1), No Match (2), or Can't Say (0). You will see different attribute variations at each stage. +{% endstep %} + +{% step %} +### Step 2: Save + +Your labels are saved automatically to `zinggDir/modelId` after each session. +{% endstep %} + +{% step %} +### Step 3: Iterate + +Run `findTrainingData` again to obtain a fresh set of candidate pairs. Label those. Repeat until Zingg's predictions align with your expectations. +{% endstep %} +{% endstepper %} + +{% tabs %} +{% tab title="Community" %} +In Community, pairs are presented serially in the terminal. Review each pair and enter your decision. + +### **Python** + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "label" +]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +### **CLI** + +```bash +./zingg.sh --phase label --conf config.json --showConcise=true +``` + +{% hint style="success" icon="right-long" %} +The `showConcise` flag only shows fields which are `NOT DONT_USE`. This makes the labelling session cleaner when you have many fields. +{% endhint %} + +### `updateLabel` section + +As your understanding of your data evolves, you may need to revisit and correct previously marked pairs. Generate model documentation first (see \[Generate Model Documentation]), then run `updateLabel`: + +#### Python + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "updateLabel" +]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### CLI + +```bash +./scripts/zingg.sh --phase updateLabel --conf +``` + +This opens the console labeler, which accepts the cluster ID of the pairs you want to update. Note: Keep a backup of your model folder before running `updateLabel`. +{% endtab %} + +{% tab title="Enterprise" %} +The `showConcise` flag only shows fields which are `NOT DONT_USE`. This makes the labelling session cleaner when you have many fields. + +#### Python + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "label" +]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +{% hint style="info" icon="right-long" %} +Enterprise provides a visual widget showing one pair at a time with Match, No Match, and Can't Say buttons. Download the diagnostics view to share match quality with stakeholders before committing to training. +{% endhint %} + +_**CHECK WITH SONAL - Please confirm the exact Python code for the interactive label widget and saveMarkedRecords() call so this tab can be completed.**_ +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +**CHECK WITH SONAL ABOUT THIS TOPIC - NEEDS ENTIRELY DIFFERENT SET OF CONTENT TO BE DISCUSSED LATER.** +{% endtab %} +{% endtabs %} diff --git a/docs/running-zingg/link-across-datasets.md b/docs/running-zingg/link-across-datasets.md new file mode 100644 index 000000000..132485dd2 --- /dev/null +++ b/docs/running-zingg/link-across-datasets.md @@ -0,0 +1,121 @@ +--- +description: >- + Run the link phase to match records across two separate datasets using your + trained Zingg model. +--- + +# Link across Datasets + +{% hint style="success" icon="right-long" %} +`Link` and `Match` are two EQUAL operations. Both use the same trained model. Neither is a subset of the other. + +* `Link` - match records across two datasets. Each record from the first source is matched with all records from the remaining sources. +* `Match` - find duplicates within one dataset. +{% endhint %} + +The link phase is used when you have two datasets that are individually duplicate-free but need to be matched against each other. Common use cases include reference data mastering, data enrichment, and linking records from different source systems. + +Link uses the same trained model as match. The output structure is identical to match output, with one additional column: `z_source`, which identifies which source dataset each record came from. + +### Output Fields + +Link output contains all input fields plus four Zingg-generated columns: + +* `Z_CLUSTER` - unique identifier shared by all records resolved as the same entity across both datasets. In Enterprise this is the persistent Zingg ID. +* `Z_MINSCORE` - lowest similarity score within the cluster. +* `Z_MAXSCORE` - highest similarity score within the cluster. +* `Z_SOURCE` - the source dataset each record came from. Use this to trace each resolved record back to its origin system. + +{% tabs %} +{% tab title="Community" %} +### Python + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "link" +]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +### CLI + +```bash +./zingg.sh --phase link --conf config.json +``` + +{% hint style="success" icon="right-long" %} +The link config file needs two data sources defined. Sample config for link available at:\ +`github.com/zinggAI/zingg/blob/main/examples/febrl/configLink.json` +{% endhint %} + +### Read and View Output + +```python +output = spark.read.csv( + "/tmp/febrlLinkOutput", + header = True +) +display(output) +``` +{% endtab %} + +{% tab title="Enterprise" %} +### Python + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "link" +]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +### CLI + +```bash +./zingg.sh --phase link --conf config.json +``` + +### Read and View Output + +```python +# Read link output +output = spark.read.csv( + "/tmp/febrlLinkOutput", + header=True +) +display(output) +``` + +{% hint style="info" icon="right-long" %} +Enterprise link output includes Zingg ID instead of `Z_CLUSTER`, plus the `Z_SOURCE` column identifying the source dataset of each record. +{% endhint %} +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +{% hint style="info" icon="right-long" %} +Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark cluster. +{% endhint %} + +_**CHECK WITH SONAL - Enterprise Snowflake content for this topic to be provivded by Sonal**_ +{% endtab %} +{% endtabs %} + +
+ +When should I use link instead of match? + +Use match when your goal is to find duplicates within a single dataset. For example, identifying that two customer records in the same CRM represent the same person. + +Use link when you have two separate datasets that are individually duplicate-free but need to be matched against each other. For example: + +* Matching a supplier list from one system against a vendor master from another +* Enriching a customer list with data from a reference dataset +* Linking records from two different source systems before consolidation + +Both match and link use the same trained model. They are equal operations, not a hierarchy. You do not need separate training for link. + +
diff --git a/docs/running-zingg/lookup-data.md b/docs/running-zingg/lookup-data.md new file mode 100644 index 000000000..1279a273d --- /dev/null +++ b/docs/running-zingg/lookup-data.md @@ -0,0 +1,67 @@ +--- +description: >- + Look up specific records in your match output to find which entity cluster + they belong to and get their Zingg ID. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# Lookup Data + +{% hint style="info" icon="right-long" %} +**Enterprise** only. The lookup feature assigns a Zingg ID to given lookup records by finding which entity cluster they belong to in the existing match output. +{% endhint %} + +After running the match phase, you sometimes need to look up specific records to determine which entity cluster they belong to. The lookup feature handles this; for given lookup records, it assigns the Zingg ID that describes which entity cluster each record belongs to. + +This is useful for operational post-match queries: looking up a new customer record to see if it already exists in your resolved entity graph or checking which cluster a specific record resolves to without re-running the full match. + +### Run the lookup phase + +```bash +./scripts/zingg.sh \ + --phase runLookup \ + --conf +``` + +### `lookupConf.json` structure + +```json +{ + "config" : "config.json", + "lookupData" + : [ {"name" : "lookup-test-data", "format" : "inMemory"} ], + "lookupOutput" : [ { + "name" : "lookup-output", + "format" : "csv", + "props" : { + "path" : "/tmp/zinggOutput/lookup", + "delimiter" : ",", + "header" : true + } + } ] +} +``` + +
+ +What do the lookupConf.json fields mean? + +* **`config`**\ + Points to your base config.json - the same configuration file used for your match phase. Zingg uses this information to understand your field definitions and model location. +* **`lookupData`**\ + The records you want to look up are available here. The format "`inMemory`" means the lookup records are provided directly in memory rather than from a file. Replace with a file-based pipe config if your lookup records are in a CSV or other format. +* **`lookupOutput`**\ + Where Zingg writes the lookup results. Each lookup record in the output will have its assigned Zingg ID showing which entity cluster it belongs to. + +
+ +**CHECK WITH SONAL -** [**https://docs.zingg.ai/latest/stepbystep/lookup**](https://docs.zingg.ai/latest/stepbystep/lookup) **-** + +**The lookup page on the live docs is minimal. Three things to confirm before publishing:** + +1. **What does the output look like? Which columns appear in lookup output?** +2. **Is lookupData always "inMemory" or can it be a file pipe (CSV, Parquet etc.)?** +3. **Is lookup available in all Enterprise tiers or only specific editions?** diff --git a/docs/running-zingg/quick-start-docker.md b/docs/running-zingg/quick-start-docker.md new file mode 100644 index 000000000..c76eb31a1 --- /dev/null +++ b/docs/running-zingg/quick-start-docker.md @@ -0,0 +1,423 @@ +--- +description: >- + Walk through the complete Zingg workflow on your local machine using Docker + install, configure, find candidate pairs, label, train, and run match. Covers + Community (open source) and Enterprise +--- + +# Quick Start (Docker) + +This page walks you through the full Zingg workflow on your local machine using Docker. You will install Zingg, connect sample data, find candidate pairs, label those pairs, train the model, and run match to see your first results. By the end you will have run every phase of the Zingg workflow and seen entity resolution working on your local machine with real data. + +{% hint style="success" icon="right-long" %} +New to entity resolution? Read [Entity Resolution](../zingg-concepts/entity-resolution/) for the problem space and why Zingg's approach works. +{% endhint %} + +{% tabs %} +{% tab title="Local Spark (Docker)" %} +{% hint style="success" icon="right-long" %} +Fastest way to get started. Uses Docker and the Zingg Python API. No Spark cluster setup required. Covers Community (open source) and Enterprise - pull the Docker image and follow the same steps for either edition. +{% endhint %} + +### Prerequisites + +* Docker installed on your machine +* At least 4 GB of free RAM +* Permission to bind-mount local directories (for output and model persistence) + +### **Step 1: Pull the Zingg Docker image and start a container** + +Pull the Zingg Docker image: + +```bash +docker pull zingg/zingg:0.5.0 +``` + +Start a bash session inside the container: + +```bash +docker run -it zingg/zingg:0.5.0 bash +``` + +If you see a permission error, use the volume mount form: + +```bash +docker run -v /tmp:/tmp -it zingg/zingg:0.5.0 bash +``` + +The Zingg Python package is already installed inside the Docker container. Zingg Python programs run via the `zingg.sh` script provided with the Zingg release. + +### **Step 2: Use the bundled sample data** + +The Docker image ships with the FEBRL sample dataset and a sample config file: + +* Sample data: `examples/febrl/test.csv` +* Sample config: `examples/febrl/config.json` (Community) or `examples/febrl/configSnow.json` (Snowflake variant) + +If you want to use your own data instead, copy it into the running container from your local machine: + +{% code expandable="true" %} +```bash +docker cp /path/to/your-data.csv :/zingg/your-data.csv +``` +{% endcode %} + +{% hint style="danger" icon="right-long" %} +Replace `` with your running container's ID (find it with `docker ps`). +{% endhint %} + +### **Step 3: Set up the configuration** + +Create or edit your configuration. The config defines your field definitions, input and output paths, model ID, and partition settings. Both editions use the same JSON structure — only the Python class differs between editions. + +{% hint style="success" icon="right-long" %} +`FUZZY` handles typos and abbreviations. `EXACT` requires character-for-character match. `DONT_USE` excludes a field from matching but keeps it in output. For all match types → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +{% endhint %} + +#### Python - Community + +```python +from zingg.client import * +from zingg.pipes import * + +args = Arguments() +args.setModelId("100") +args.setZinggDir("models") +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.5) + +# Field definitions + +fname = FieldDefinition("fname", "string", MatchType.FUZZY) +lname = FieldDefinition("lname", "string", MatchType.FUZZY) +stNo = FieldDefinition("stNo", "string", MatchType.FUZZY) +add1 = FieldDefinition("add1", "string", MatchType.FUZZY) +add2 = FieldDefinition("add2", "string", MatchType.FUZZY) +city = FieldDefinition("city", "string", MatchType.FUZZY) +areacode = FieldDefinition("areacode", "string", MatchType.FUZZY) +state = FieldDefinition("state", "string", MatchType.FUZZY) +dob = FieldDefinition("dob", "string", MatchType.EXACT) +ssn = FieldDefinition("ssn", "string", MatchType.EXACT) + +fieldDefs = [fname, lname, stNo, add1, add2, city, areacode, state, dob, ssn] +args.setFieldDefinition(fieldDefs) + +# Input schema and pipe +schema = "id string, fname string, lname string, stNo string, add1 string, add2 string, city string, areacode string, state string, dob string, ssn string" + +inputPipe = CsvPipe("testFebrl", "examples/febrl/test.csv", schema) +args.setData(inputPipe) + +# Output pipe + +outputPipe = CsvPipe("resultFebrl", "/tmp/febrlOutput") +args.setOutput(outputPipe) + +``` + +#### Python - Enterprise + +```python +from zingg.client import * +from zingg.pipes import * +from zinggEC.enterprise.common.epipes import * +from zinggEC.enterprise.common.EArguments import * +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + +args = EArguments() +args.setModelId("100") +args.setZinggDir("models") +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.5) + +# Field definitions + +fname = EFieldDefinition("fname", "string", MatchType.FUZZY) +lname = EFieldDefinition("lname", "string", MatchType.FUZZY) +stNo = EFieldDefinition("stNo", "string", MatchType.FUZZY) +add1 = EFieldDefinition("add1", "string", MatchType.FUZZY) +add2 = EFieldDefinition("add2", "string", MatchType.FUZZY) +city = EFieldDefinition("city", "string", MatchType.FUZZY) +areacode = EFieldDefinition("areacode", "string", MatchType.FUZZY) +state = EFieldDefinition("state", "string", MatchType.FUZZY) +dob = EFieldDefinition("dob", "string", MatchType.EXACT) +ssn = EFieldDefinition("ssn", "string", MatchType.EXACT) + +fieldDefs = [fname, lname, stNo, add1, add2, city, areacode, state, dob, ssn] +args.setFieldDefinition(fieldDefs) + +# Input schema and pipe +schema = "id string, fname string, lname string, stNo string, add1 string, add2 string, city string, areacode string, state string, dob string, ssn string" + +inputPipe = ECsvPipe("testFebrl", "examples/febrl/test.csv", schema) +args.setData(inputPipe) + +# Output pipe + +outputPipe = ECsvPipe("resultFebrl", "/tmp/febrlOutput") +outputPipe.setHeader("true") +args.setOutput(outputPipe) +``` + +{% hint style="danger" icon="right-long" %} +The JSON config blocks below are the equivalent declarations of the Python above. Use either approach - Python API for in-notebook orchestration, JSON for shell-driven workflows. Community and Enterprise use the same JSON structure with the addition of `outputStats` in Enterprise. +{% endhint %} + +#### JSON - Community + +```json +{ + "fieldDefinition": [ + {"fieldName": "fname", "matchType": "fuzzy", + "fields": "fname", "dataType": "string"}, + {"fieldName": "lname", "matchType": "fuzzy", + "fields": "lname", "dataType": "string"}, + {"fieldName": "stNo", "matchType": "fuzzy", + "fields": "stNo", "dataType": "string"}, + {"fieldName": "add1", "matchType": "fuzzy", + "fields": "add1", "dataType": "string"}, + {"fieldName": "add2", "matchType": "fuzzy", + "fields": "add2", "dataType": "string"}, + {"fieldName": "city", "matchType": "fuzzy", + "fields": "city", "dataType": "string"}, + {"fieldName": "areacode", "matchType": "fuzzy", + "fields": "areacode", "dataType": "string"}, + {"fieldName": "state", "matchType": "fuzzy", + "fields": "state", "dataType": "string"}, + {"fieldName": "dob", "matchType": "exact", + "fields": "dob", "dataType": "string"}, + {"fieldName": "ssn", "matchType": "exact", + "fields": "ssn", "dataType": "string"} + ], + "data": [{ + "name": "testFebrl", + "format": "csv", + "props": { + "location": "examples/febrl/test.csv", + "delimiter": ",", + "header": "false" + } + }], + "output": [{ + "name": "resultFebrl", + "format": "csv", + "props": { + "location": "/tmp/febrlOutput", + "delimiter": ",", + "header": "true" + } + }], + "modelId": "100", + "zinggDir": "models", + "numPartitions": 4, + "labelDataSampleSize": 0.5 +} +``` + +#### JSON - Enterprise + +```json +{ + "fieldDefinition": [ + { + "fieldName": "fname", + "matchType": "fuzzy", + "fields": "fname", "dataType": "string"}, + {"fieldName": "lname", "matchType": "fuzzy", + "fields": "lname", "dataType": "string"}, + {"fieldName": "stNo", "matchType": "fuzzy", + "fields": "stNo", "dataType": "string"}, + {"fieldName": "add1", "matchType": "fuzzy", + "fields": "add1", "dataType": "string"}, + {"fieldName": "add2", "matchType": "fuzzy", + "fields": "add2", "dataType": "string"}, + {"fieldName": "city", "matchType": "fuzzy", + "fields": "city", "dataType": "string"}, + {"fieldName": "areacode", "matchType": "fuzzy", + "fields": "areacode", "dataType": "string"}, + {"fieldName": "state", "matchType": "fuzzy", + "fields": "state", "dataType": "string"}, + {"fieldName": "dob", "matchType": "exact", + "fields": "dob", "dataType": "string"}, + {"fieldName": "ssn", "matchType": "exact", + "fields": "ssn", "dataType": "string"} + ], + "data": [{ + "name": "testFebrl", + "format": "csv", + "props": { + "location": "examples/febrl/test.csv", + "delimiter": ",", + "header": "false" + } + }], + "output": [{ + "name": "resultFebrl", + "format": "csv", + "props": { + "location": "/tmp/febrlOutput", + "delimiter": ",", + "header": true + } + }], + "outputStats": { + "name": "stats", + "format": "csv", + "props": { + "location": "/tmp/zinggStats_$ZINGG_DYNAMIC_STAT_NAME", + "delimiter": ",", + "header": true + } + }, + "modelId": "100", + "zinggDir": "models", + "numPartitions": 4, + "labelDataSampleSize": 0.5 +} +``` + +{% hint style="info" icon="right-long" %} +`outputStats` is Enterprise-only. The `$ZINGG_DYNAMIC_STAT_NAME` placeholder is replaced at runtime with `SUMMARY`, `CLUSTER`, or `RECORD` for the three different stats files Zingg produces. + +For full configuration schema with all parameters → [Configuration Schema](../reference/configuration-schema.md) +{% endhint %} + +### **Step 4: Find candidate pairs** + +Zingg scans your dataset using the field rules defined in Step 3 and selects the most informative pairs for labeling - edge cases where the model has the most to learn. Candidate pairs are written to `zinggDir/modelId`. + +#### **Python - Community** + +```python +options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) + +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### **Python - Enterprise** + +```python +options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) + +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### CLI (both editions) + +```bash +./zingg.sh --phase findTrainingData --conf config.json +``` + +### **Step 5: Label pairs** + +Zingg shows you the pairs selected by `findTrainingData`. For each pair, decide: + +* `1` - Match: these records represent the same real-world entity +* `0` - Not a match: these records are different entities +* `2` - Not sure: when you cannot decide + +{% hint style="success" icon="right-long" %} +Zingg selects the most informative pairs from your data - not random samples. Label until all field types and data variation patterns in your schema are represented. Repeat Steps 4–5 in a loop if needed. If accuracy needs improvement after the first match run, return to labeling and focus on patterns that are missing or underrepresented. +{% endhint %} + +#### **Python - Community** + +```python +options = ClientOptions([ ClientOptions.PHASE, "label" ]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### **Python - Enterprise** + +```python +options = ClientOptions([ ClientOptions.PHASE, "label" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### CLI (both editions) + +```bash +./zingg.sh --phase label --conf config.json --showConcise=true +``` + +The `--showConcise=true` flag shows only fields used for matching and hides `DONT_USE` fields. + +### **Step 6: Train the model** + +Once you have enough labelled pairs, run the `train` phase. Zingg builds blocking and similarity models from your labelled training data and persists them to `zinggDir/modelId`. These models can be reused on newer datasets without retraining. + +#### **Python - Community** + +```python +options = ClientOptions([ ClientOptions.PHASE, "train" ]) + +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### **Python - Enterprise** + +```python +options = ClientOptions([ ClientOptions.PHASE, "train" ]) + +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### CLI (both editions) + +```bash +./zingg.sh --phase train --conf config.json +``` + +### **Step 7: Run match and resolve identities** + +Run the `match` phase. Zingg applies the trained models to your full dataset and writes matched records to the output location configured in Step 3. + +#### **Python - Community** + +```python +options = ClientOptions([ ClientOptions.PHASE, "match" ]) + +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### **Python - Enterprise** + +```python +options = ClientOptions([ ClientOptions.PHASE, "match" ]) + +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### CLI (both editions) + +```bash +./zingg.sh --phase match --conf config.json +``` + +### **Reading the match output** + +Zingg adds output columns to your input. Community produces `Z_CLUSTER`, `Z_MINSCORE`, and `Z_MAXSCORE`. Enterprise produces `ZINGG_ID` (a persistent GUID), `Z_MINSCORE`, and `Z_MAXSCORE`. + +For threshold guidance and full output column definitions → [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +Completed the walkthrough? Next steps: + +* Connect your own data - [Connect Data](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/connect-your-data) +* Full configuration reference - [Configure Zingg](configure-zingg.md) +* Understanding output scores - [Interpreting output](../interpreting-results/interpret-output-scores.md) +{% endhint %} + +{% hint style="warning" icon="right-long" %} +Enterprise Quick Start uses EArguments, ECsvPipe, and EZingg in 7 production-grade notebooks. [Talk to us about Enterprise](https://www.zingg.ai/company/contact/contact) to get access to the full notebook sequence. +{% endhint %} diff --git a/docs/running-zingg/reassign-zingg-id.md b/docs/running-zingg/reassign-zingg-id.md new file mode 100644 index 000000000..d62ac4d0d --- /dev/null +++ b/docs/running-zingg/reassign-zingg-id.md @@ -0,0 +1,228 @@ +--- +description: >- + Preserve Zingg IDs when switching to a new trained model or migrating + infrastructure without disrupting downstream systems. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# Reassign Zingg ID + +{% hint style="info" icon="right-long" %} +Enterprise Plus only. Preserves Zingg IDs when switching to a new model. Maximises ID continuity and minimises downstream disruption. Available in ZinggES (Enterprise Plus). +{% endhint %} + +When you evolve your Zingg setup - upgrading your model, migrating infrastructure, changing data schemas, or moving to a new platform; you want to preserve the Zingg IDs already flowing through your production systems. + +The `reassignZinggId` phase carries Zingg IDs from your original production model to a new model's output. It maps clusters from the new model back to the original cluster assignments and preserves the original Zingg IDs wherever clusters overlap. New IDs are generated only where no match is found. + +{% hint style="info" icon="right-long" %} +For what the Zingg ID is and how it works in Enterprise output → [Zingg ID and Z Cluster](../zingg-concepts/z-cluster-and-zingg-id.md) +{% endhint %} + +### When to use Reassign Zingg ID + +
+ +Model upgrades + +When you add new features like nickname support, improve blocking strategies, or tune classifiers, the new model may produce different clusters than the original. Use `reassignZinggId` to carry over original IDs to the new model output wherever clusters overlap, so downstream systems are not disrupted. + +
+ +
+ +Infrastructure migration + +Are you moving from Spark to Snowflake or from Databricks to Microsoft Fabric?\ +Use `reassignZinggId` to maintain the same Zingg IDs across platforms when the underlying data and model are otherwise equivalent. + +
+ +
+ +Schema changes + +If your data schema changes, new fields are added, fields are removed, or field types are updated. Use reassignZinggId to preserve IDs for records that can still be matched via primary key to the original output. + +
+ +
+ +Data migration + +When migrating data between systems or consolidating data sources, `reassignZinggId` maintains ID consistency across the migration. Downstream systems that reference Zingg IDs continue to work without any updates. + +
+ +### How it works + +The reassign phase compares two configurations: + +* **Original configuration**: your production model with established Zingg IDs. +* **New configuration**: your updated model with improved features, new infrastructure, or schema changes. + +The reassign phase then, + +1. Identifies clusters in the new output that overlap with original clusters via primary key matching +2. Reassigns the original Zingg IDs to matching clusters wherever possible +3. Generates new Zingg IDs only when no match is found in the original clusters + +### Worked example + +**Original model (production):** 4 records in a cluster, Zingg ID = `ZID-7f3a`. Records: A, B, C, D. + +**New model (upgraded):** 6 records in the equivalent cluster. Records: A, B, C, D, E, F (E and F are new records added since the last full match). + +**After `reassignZinggId`:** + +* Records A, B, C, D → `ZID-7f3a` (Zingg ID from the original output preserved via primary key overlap) +* Records E, F → `ZID-9b2c` (new Zingg IDs generated for records with no match in the original) + +Downstream systems that reference `ZID-7f3a` continue to work without any change. + +{% tabs %} +{% tab title="Enterprise Python" %} +### **Step 1: Define the original (baseline) configuration** + +Set up `EArguments` matching your existing production model. This is the model whose Zingg IDs you want to preserve. + +```python +from zingg.client import* +from zingg.pipes import* +from zinggEC.enterprise.common.EArguments import* +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition +from zinggES.enterprise.spark.ESparkClient import* +from zinggEC.enterprise.common.TransformedOutputArguments import* +from zinggEC.enterprise.common.EClientOptions import* + +originalArgs = EArguments() + +id = EFieldDefinition("id", "string", MatchType.DONT_USE) +id.setPrimaryKey(True) +fname = EFieldDefinition("fname", "string", MatchType.FUZZY) +lname = EFieldDefinition("lname", "string", MatchType.FUZZY) + +originalArgs.setFieldDefinition([id, fname, lname]) +originalArgs.setModelId("107") +originalArgs.setZinggDir("./models") +originalArgs.setNumPartitions(4) + +schema = ( + "id string, fname string, " + "lname string, stNo string, " + "add1 string, city string, " + "areacode string, state string, " + "dob string, ssn string" +) + +originalInputPipe = ECsvPipe( + "originalData", + "examples/febrl5M/febrl_data.csv", + schema +) +originalArgs.setData(originalInputPipe) + +originalOutputPipe = ECsvPipe("originalOutput", "/tmp/zinggOutputOriginal") +originalOutputPipe.setHeader("true") +originalArgs.setOutput(originalOutputPipe) +``` + +Add all field definitions from your original production config - only a few are shown above as a pattern. + +### **Step 2: Define the new (updated) configuration** + +Set up `EArguments` for your new model. This is the model that produced the new cluster assignments which need Zingg IDs reassigned. + +```python +newArgs = EArguments() +newArgs.setModelId("999") +newArgs.setZinggDir("./models") +``` + +Add all field definitions from your new model config - only the structure is shown above. + +### **Step 3: Run `trainMatch` on the new configuration first** + +The new model must produce its own match output before reassign can compare clusters. + +```python +options = EClientOptions([ + EClientOptions.PHASE, + "trainMatch" +]) +zinggNew = EZingg(newArgs, options) +zinggNew.initAndExecute() +``` + +### **Step 4: Create `TransformedOutputArguments`** + +`TransformedOutputArguments` wraps both configurations and the destination for the reassigned output. + +```python +reassignArgs = TransformedOutputArguments() +reassignArgs.setParentArgs(newArgs) +reassignArgs.setOriginalArgs(originalArgs) + +reassignOutputPipe = ECsvPipe("reassignedOutput", "/tmp/zinggReassigned") +reassignOutputPipe.setHeader("true") +reassignArgs.setTransformedOutputPath(reassignOutputPipe) +``` + +### Step 5: Execute `reassignZinggId` + +```python +reassignOptions = EClientOptions([ + EClientOptions.PHASE, + "reassignZinggId" +]) +zinggReassign = EZingg(reassignArgs, reassignOptions) +zinggReassign.initAndExecute() +``` +{% endtab %} + +{% tab title="Enterprise JSON" %} +### **Configuration wrapper (`configReassign.json`)** + +The JSON wrapper config references your new model config and specifies the destination for the reassigned output. + +```json +{ + "config": "$ZINGG_ENT_REPO$/spark/examples/febrl5M/configUpdated.json", + "transformedOutputPath": { + "name": "reassignedOutput", + "format": "csv", + "props": { + "location": "/tmp/zinggTransformedOutputReassigned5M", + "delimiter": ",", + "header": true + } + } +} +``` + +* `--conf` - the wrapper config (`configReassign.json`) that references your new model +* `--originalZinggId` - your original production configuration +* `--properties-file` - optional Zingg properties file + +The CLI command takes three arguments: + +```bash +./scripts/zingg.sh \ + --phase reassignZinggId \ + --conf examples/febrl/sparkIncremental/configReassign5M.json \ + --originalZinggId examples/febrl5M/config.json \ + --properties-file config/zingg.conf +``` + +{% hint style="info" icon="right-long" %} +The `--originalZinggId` flag takes the path to your original production config file. Despite the flag name, you are passing the **configuration file** that defined the original model - Zingg uses that config to locate the original output containing the Zingg IDs to be preserved. +{% endhint %} +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +_**CHECK WITH SONAL - Enterprise Snowflake content for this topic to be provivded by Sonal**_ +{% endtab %} +{% endtabs %} diff --git a/docs/running-zingg/run-incremental-matching.md b/docs/running-zingg/run-incremental-matching.md new file mode 100644 index 000000000..ba16d1cc9 --- /dev/null +++ b/docs/running-zingg/run-incremental-matching.md @@ -0,0 +1,231 @@ +--- +description: >- + Update the identity graph with new, changed, or deleted records without + re-running the full match on your entire dataset. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# Run Incremental Matching + +{% hint style="info" icon="right-long" %} +**Enterprise only.** Incremental matching updates the identity graph and preserves Zingg IDs across runs. Not available in Community. +{% endhint %} + +{% hint style="danger" icon="right-long" %} +**BEFORE YOU BEGIN:** The initial match phase must have been completed first. Run incremental only after a full match has been run on your base dataset. + +Output is written to the same location as your match output. There is no separate output path for\ +incremental results. +{% endhint %} + +Re-running matching on entire datasets is wasteful and loses the lineage of matched records against a persistent identifier. Incremental matching lets you process new, updated, and deleted\ +records and match them to existing clusters, without starting over. + +New records that do not find a match receive their own new Zingg ID. Records that match an existing cluster are assigned that cluster's Zingg ID. Cluster merges, unmerges, and assignments happen automatically. Human-approved cluster decisions from previous runs are preserved and not overridden. + +{% tabs %} +{% tab title="Enterprise Python" %} +### Step 1: Imports + +```python +from zingg.client import * +from zingg.pipes import * +from zinggEC.enterprise.common.epipes import * +from zinggEC.enterprise.common.EArguments import * +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition +from zinggEC.enterprise.common.IncrementalArguments import * +from zinggES.enterprise.spark.ESparkClient import * +``` + +### Step 2: Set up base args + +```python +args = EArguments() +recId = EFieldDefinition( + "recId", + "string", + MatchType.DONT_USE +) +recId.setPrimaryKey(True) + +fname = EFieldDefinition( + "fname", + "string", + MatchType.FUZZY +) +lname = EFieldDefinition( + "lname", + "string", + MatchType.FUZZY +) +stNo = EFieldDefinition( + "stNo", + "string", + MatchType.FUZZY +) +add1 = EFieldDefinition( + "add1", + "string", + MatchType.FUZZY +) +add2 = EFieldDefinition( + "add2", + "string", + MatchType.FUZZY +) +city = EFieldDefinition( + "city", + "string", + MatchType.FUZZY +) +areacode = EFieldDefinition( + "areacode", + "string", + MatchType.FUZZY +) +state = EFieldDefinition( + "state", + "string", + MatchType.FUZZY +) +dob = EFieldDefinition( + "dob", + "string", + MatchType.FUZZY +) +ssn = EFieldDefinition( + "ssn", + "string", + MatchType.FUZZY +) + +fieldDefs = [ + recId, + fname, + lname, + stNo, + add1, + add2, + city, + areacode, + state, + dob, + ssn +] +args.setFieldDefinition(fieldDefs) +args.setModelId("100") +args.setZinggDir("/tmp/models") +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.5) + +schema = ( + "recId string, fname string, " + "lname string, stNo string, add1 string, " + "add2 string, city string, areacode string, " + "state string, dob string, ssn string" +) + +inputPipe = ECsvPipe("testFebrl", "examples/febrl/test.csv", schema) +args.setData(inputPipe) + +outputPipe = ECsvPipe("resultFebrl", "/tmp/febrlOutput") +outputPipe.setHeader("true") +args.setOutput(outputPipe) +``` + +### Step 3: Create `IncrementalArguments` + +```python +incrArgs = IncrementalArguments() +incrArgs.setParentArgs(args) +``` + +{% hint style="success" icon="right-long" %} +`setParentArgs()` inherits all base configuration from notebook 01 - field definitions, model ID, zinggDir, and output pipe. You only need to configure the incremental input pipe separately. +{% endhint %} + +### Step 4: Configure incremental input pipe + +```python +incrPipe = ECsvPipe("testFebrlIncr", "examples/febrl/test-incr.csv", schema) +incrArgs.setIncrementalData(incrPipe) + +outputTmpPipe = ECsvPipe("outputTmp", "/tmp/zinggOutput_febrl_tmp") +outputTmpPipe.setHeader("true") +incrArgs.setOutputTmp(outputTmpPipe) +``` + +### Step 5: Run incremental matching + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "runIncremental" +]) +zingg = EZingg(incrArgs, options) +zingg.initAndExecute() +``` + +### Step 6: Read output + +```python +output = spark.read.csv( + "/tmp/febrlOutput", + header = True +) +display(output) +``` + +{% hint style="info" icon="right-long" %} +Output is at the same path as your match output. New records get new Zingg IDs. Records matching existing clusters get the existing Zingg ID. No separate output path is needed. +{% endhint %} +{% endtab %} + +{% tab title="Enterprise JSON" %} +If using the CLI instead of the Python API, create an `incrementalConf.json` file: + +```json +{ + "config": "config.json", + "incrementalData": [ + { + "name": "customers_incr", + "format": "csv", + "props": { + "path": "test-incr.csv", + "delimiter": ",", + "header": false + }, + "schema": "recId string, fname string, lname string, stNo string, add1 string, add2 string, city string, state string, areacode string, dob string, ssn string" + } + ], + "outputTmp": { + "name": "customers_incr_temp", + "format": "csv", + "props": { + "location": "/tmp/zinggOutput_febrl_tmp", + "delimiter": ",", + "header": true + } + } +} +``` + +**Run With** + +```bash +./scripts/zingg.sh \ + --phase runIncremental \ + --conf +``` + +The `outputTmp` section specifies a temporary output location where Zingg writes intermediate results before final processing or merging with the main output. +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +_**CHECK WITH SONAL - Enterprise Snowflake content for this topic to be provivded by Sonal**_ +{% endtab %} +{% endtabs %} diff --git a/docs/running-zingg/run-the-match-phase.md b/docs/running-zingg/run-the-match-phase.md new file mode 100644 index 000000000..7c7ad4cdc --- /dev/null +++ b/docs/running-zingg/run-the-match-phase.md @@ -0,0 +1,119 @@ +--- +description: >- + Run the match phase to find duplicate records within a single dataset using + your trained Zingg model. +--- + +# Run the match phase + +The `match` phase runs AFTER `train`. It applies the trained Zingg model to your full dataset and groups records that represent the same real-world entity into clusters. + +Use `match` when you want to find duplicates within a single dataset. If you need to match records across two separate datasets, use the link phase instead - both are equal operations using the same trained model. + +### Output Fields + +Every record in the match output contains your original input fields plus three columns added by Zingg: + +* `Z_CLUSTER` - unique identifier shared by all records in the same cluster. Records with the same `Z_CLUSTER` represent the same real-world entity. In Enterprise, this is the persistent\ + Zingg ID. +* `Z_MINSCORE` - the lowest similarity score between any two records in that cluster. Indicates the confidence of the weakest link in the cluster. +* `Z_MAXSCORE` - the highest similarity score between any two records in that cluster. Indicates the strongest match within the cluster. + +{% hint style="success" icon="right-long" %} +**Read more:** + +* Scores are explained in detail - [Interpreting output scores](../interpreting-results/interpret-output-scores.md) +* For the link phase (across two datasets) - [Link across datasets](link-across-datasets.md) +{% endhint %} + +{% tabs %} +{% tab title="Community" %} +### Python + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "match" +]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +### CLI + +```bash +./zingg.sh --phase match --conf config.json +``` + +### Read and View Output + +```python +output = spark.read.csv( + "/tmp/febrlOutput", + header = True +) +display(output) +``` + +{% hint style="info" icon="right-long" %} +Matching records share the same `Z_CLUSTER` value. + +`Z_MINSCORE` and `Z_MAXSCORE` show match confidence within the cluster. +{% endhint %} +{% endtab %} + +{% tab title="Enterprise" %} +### Python + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "match" +]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +### CLI + +```bash +./zingg.sh --phase match --conf config.json +``` + +### Read and View Output + +```python +# Read match output +output = spark.read.csv( + "/tmp/febrlOutput", + header=True +) +display(output) +``` + +{% hint style="info" icon="right-long" %} +Enterprise output includes `Zingg ID` (persistent across runs) instead of `Z_CLUSTER`, plus deterministic match flag and Match Statistics. `Zingg ID` is stable across all subsequent incremental runs. +{% endhint %} +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +{% hint style="info" icon="right-long" %} +Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark cluster. +{% endhint %} + +_**CHECK WITH SONAL - Enterprise Snowflake content for this topic to be provivded by Sonal**_ +{% endtab %} +{% endtabs %} + +
+ +How do I interpret Z_MINSCORE and Z_MAXSCORE? + +`Z_MINSCORE` and `Z_MAXSCORE` are the confidence range for a cluster. + +* `Z_MINSCORE` is the lowest similarity score between any two records in the cluster. A very low `Z_MINSCORE` means some records in the cluster matched weakly—worth reviewing manually. +* `Z_MAXSCORE` is the highest similarity score between any pair in the cluster. The threshold is automatically optimized by Zingg so you do not need to tune a cut-off manually. You may see records with scores below the conventional 0.5; this behaviour is intentional as Zingg optimizes for both accuracy and recall. + +**Recommended approach**: Keep clusters whose value `Z_MINSCORE` is 0 for manual inspection. Keep the cluster size above 4 or 5 for closer review. The exact threshold depends on how accurate you determine your results and how much manual control you want. + +
diff --git a/docs/running-zingg/step-by-step-guide.md b/docs/running-zingg/step-by-step-guide.md new file mode 100644 index 000000000..9a8a2283c --- /dev/null +++ b/docs/running-zingg/step-by-step-guide.md @@ -0,0 +1,135 @@ +--- +description: >- + The complete Zingg workflow from setup to match output is your mental model + for the entire Zingg docs site. +--- + +# Step-by-Step Guide + +Zingg works in phases. Each phase performs a specific task and produces an output that the next phase depends on. You do not need to run all phases every time. Once the model is trained, you run `match`, `link`, or `runIncremental` directly. + +The phases below represent the complete workflow from first setup to production matching. This page describes the workflow you follow once Zingg is installed. + +{% hint style="success" icon="right-long" %} +This page describes the workflow you follow once Zingg is installed. \ +For installing Zingg on your platform → [Install Zingg](install-zingg.md). + +New to entity resolution or want to understand the problem space before diving in? → E[ntity Resolution](../zingg-concepts/entity-resolution/) +{% endhint %} + +{% stepper %} +{% step %} +### Connect your data + +Configure input and output pipes to tell Zingg where your source data lives and where to write results. Zingg connects to the data where it lives - it does not move it. + +{% hint style="success" icon="right-long" %} +**Read more**: [Content your Data](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/connect-your-data) +{% endhint %} +{% endstep %} + +{% step %} +### Configure Zingg + +Define your field definitions, match types, model ID, partition count, and any optional Enterprise features (deterministic matching, primary key, pass through, standardization). The configuration drives every downstream phase. + +{% hint style="success" icon="right-long" %} +**Read more**: [Content your Data](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/connect-your-data) +{% endhint %} +{% endstep %} + +{% step %} +### Find and Label Training Pairs + +Run `findTrainingData` to generate candidate pairs, then label each pair as Match, No Match, or Uncertain. Zingg selects the most informative pairs, not random samples. Repeat the find-and-label cycle until all field types and data variation patterns in your schema are represented in your labeled data. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* [Create Training Data](create-training-data.md) +* [Label Training Pairs](label-training-pairs.md) +{% endhint %} +{% endstep %} + +{% step %} +### Verify Blocking + +Run `verifyBlocking` to check what percentage of your known matching pairs are being blocked together correctly. Run after labelling and before committing to a full training run. If coverage is low, return to labelling. + +{% hint style="success" icon="right-long" %} +**Read more**: [Verify Blocking](verify-blocking.md) +{% endhint %} +{% endstep %} + +{% step %} +### Generate Model Documentation + +Run `generateDocs` to produce a human-readable HTML report of your training data, including pairs labeled as matches and non-matches. Useful for sharing with subject-matter experts before training. + +{% hint style="success" icon="right-long" %} +**Read more**: [Generate Model Documentation](generate-model-documentation.md) +{% endhint %} +{% endstep %} + +{% step %} +### Build and Save the Model + +Run `train` to build and save the blocking and similarity models from your labelled training data. Models are written to `zinggDir/modelId` and can be reused on new data without retraining. + +{% hint style="success" icon="right-long" %} +**Read more**: [Build and Save the Model](build-and-save-the-model.md) +{% endhint %} +{% endstep %} + +{% step %} +### Run the Match Phase or Link across Datasets + +Apply the trained model to your data: + +* Run `match` to find duplicates within a single dataset +* Run `link` to match records across two or more separate datasets + +`match` and `link` are two equal operations using the same trained model. As long as your input columns and field types are not changing, you do not need to rebuild the model. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* [Run the Match Phase](run-the-match-phase.md) +* [Link across Datasets](link-across-datasets.md) +{% endhint %} +{% endstep %} + +{% step %} +### Interpret Output Scores + +Review output scores to understand match quality. Use `Z_MINSCORE` and `Z_MAXSCORE` to set confidence thresholds for automated vs human-reviewed clusters. + +{% hint style="success" icon="right-long" %} +**Read more**: [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) +{% endhint %} +{% endstep %} + +{% step %} +### Improve Accuracy (if needed) + +If match results need improvement, return to find-and-label with focused training data for the patterns being missed, then retrain. Remove stopwords from fields like addresses and company names to improve blocking. Use custom blocking and similarity for specialized data patterns. + +{% hint style="success" icon="right-long" %} +**Read more**: [Improve Accuracy](../tuning/improve-accuracy/) +{% endhint %} +{% endstep %} +{% endstepper %} + +{% hint style="warning" icon="right-long" %} +Enterprise: 7 production-grade notebooks, one per phase, with step-isolated execution and\ +full Python API support. [Talk to us about Enterprise](https://www.zingg.ai/company/contact/contact) +{% endhint %} + +{% hint style="success" icon="right-long" %} +**Read more**: + +* Understanding the concepts behind each phase - [Overview section](https://app.gitbook.com/o/kn0G4kXLdlfPagjso48S/s/4FvYw4VaCJcugJzWCiLX/) | [Concepts glossary](../zingg-concepts/concept-glossary.md) +* [Configure Zingg](configure-zingg.md) - full configuration reference +* [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) - every output column and how to read scores +* [Zingg\_ID and Z\_Cluster](../zingg-concepts/z-cluster-and-zingg-id.md) - Community vs Enterprise identifiers in your output +{% endhint %} diff --git a/docs/running-zingg/verify-blocking.md b/docs/running-zingg/verify-blocking.md new file mode 100644 index 000000000..4fb5fbfa0 --- /dev/null +++ b/docs/running-zingg/verify-blocking.md @@ -0,0 +1,96 @@ +--- +description: >- + Verify that your blocking model is grouping known matching pairs into the same + block before training and after. +--- + +# Verify Blocking + +The blocking model filters your dataset before any similarity comparisons happen. Records in different blocks are never compared. This is what makes Zingg scalable, but it also means that if the blocking model puts two matching records in different blocks, those records will never be matched no matter how well the similarity model is trained. + +`verifyBlocking` tells you what percentage of your known matching pairs are being blocked together correctly. Run this test after configuring Zingg and again after training if you suspect missed matches. + +{% hint style="success" icon="right-long" %} +Run `verifyBlocking` in two situations: + +1. **Before labeling** - to confirm your blocking is covering expected matches before you invest in training data +2. **After training** - if your match results are missing pairs you know should be there +{% endhint %} + +### Run the `verifyBlocking` phase + +{% tabs %} +{% tab title="Community" %} +### Python + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "verifyBlocking" +]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +### CLI + +```bash +./scripts/zingg.sh --phase verifyBlocking --conf config.json +``` +{% endtab %} + +{% tab title="Enterprise" %} +### Python + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "verifyBlocking" +]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +### CLI + +```bash +./scripts/zingg.sh --phase verifyBlocking --conf config.json +``` +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +**CHECK WITH SONAL ABOUT THIS TOPIC - NEEDS ENTIRELY DIFFERENT SET OF CONTENT TO BE DISCUSSED LATER.** +{% endtab %} +{% endtabs %} + +### **Reading the output** + +`verifyBlocking` produces two output directories under `zinggDir/modelId/blocks/timestamp/`: + +* `counts` - block size distribution +* `blockSamples` - sample records from each block + +Read the output to inspect block coverage: + +```python +verify_output = spark.read.parquet( + f "{zinggDir}/{modelId}/verifyBlocking" +) +verify_output.show() +``` + +### **What to do if coverage is low** + +If `verifyBlocking` shows that many known matching pairs are not being blocked together, try these in order: + +1. **Add more labelled training pairs** of the type being missed. The blocking model learns from the same training data as the similarity model. +2. **Check your field match types.** Fields set to `DONT_USE` are excluded from blocking. If a key identity field is set to `DONT_USE`, matching pairs that differ on other fields may end up in different blocks. + +{% hint style="success" icon="right-long" %} +**Consider custom blocking functions** for specialised data patterns → [Custom Blocking and Similarity](../tuning/custom-blocking-and-similarity.md) + +**Read more**: + +* Blocking model concept and how it fits in the pipeline → [Blocking Model](../zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) +* Custom blocking functions for advanced tuning → [Custom Blocking and Similarity](../tuning/custom-blocking-and-similarity.md) +{% endhint %} diff --git a/docs/security-and-privacy/security-and-privacy.md b/docs/security-and-privacy/security-and-privacy.md new file mode 100644 index 000000000..9167484c9 --- /dev/null +++ b/docs/security-and-privacy/security-and-privacy.md @@ -0,0 +1,51 @@ +--- +description: >- + How Zingg handles sensitive data, what is stored, and what security + considerations apply to production deployments. +--- + +# Security and Privacy + +Zingg runs entirely within your infrastructure. Whether you are running Zingg Community on Spark, Zingg Enterprise on a managed Spark platform like Databricks or Fabric, or Zingg Enterprise on Snowflake using Snowpark, all data processing happens inside your environment. Nothing is transmitted to external services by the Zingg engine itself. + +This page covers data residency, where Zingg writes its artefacts, how to handle sensitive fields, and how Zingg fits into GDPR and CCPA workflows. + +### Data residency + +Zingg reads from and writes to the paths or tables you configure via `config.json` or Python API setup for Spark deployments and Snowflake schemas for Enterprise Snowflake deployments. Nothing leaves your infrastructure. + +{% hint style="success" icon="right-long" %} +Ensure your configured paths and tables sit within your security perimeter - private S3 buckets, GCS buckets with appropriate IAM, ADLS or OneLake with Entra ID controls, or Snowflake schemas with role-based access controls. +{% endhint %} + +#### Zingg Artefacts in Your Environment + +
ArtefactSpark deployment locationSnowflake Enterprise locationContents
Training data - unmarkedzinggDir/modelId/trainingData/unmarked/TO BE ADDEDCandidate record pairs selected by Zingg for labelling. Contains field values from your input dataset plus Zingg metadata columns.
Training data - markedzinggDir/modelId/trainingData/marked/TO BE ADDEDPairs after you have labelled them as Match, No Match, or Uncertain. Same field columns as unmarked plus the label column.
Trained modelzinggDir/modelId/model/TO BE ADDEDSpark ML model artefacts encoding the blocking and similarity learned from your training data. Does not contain raw input records.
Match outputThe output path configured in your EArguments / ArgumentsTO BE ADDEDAll input fields plus Zingg-generated columns (Z_CLUSTER, Z_MINSCORE, Z_MAXSCORE in Community; ZINGG_ID and Enterprise equivalents in Enterprise).
Stopwords (if configured)zinggDir/modelId/stopWords/<columnName>TO BE ADDEDList of high-frequency words detected for the specified column.
Output statistics (Enterprise)The outputStats path configuredTO BE ADDEDThree statistics files per run: summary, cluster, and record-level metrics.
CredentialsNot stored by ZinggTO BE ADDEDConnection credentials in config.json or environment variables remain under your control. Snowflake Enterprise uses Snowflake's native role and credential model.
+ +### Handling sensitive fields + +Fields containing PII such as SSN, national ID, date of birth, or financial identifiers should be\ +handled carefully: + +* Use `MatchType.EXACT` for sensitive identifier fields rather than `FUZZY`. `EXACT` matching does not expose partial field values through similarity scoring. +* Use `MatchType.DONT_USE` to exclude a field from matching entirely while still including it in output. This is useful for fields that are present in your data but should not influence entity resolution decisions. +* Consider pseudonymizing or tokenizing highly sensitive fields before running Zingg if your data governance policy requires it. Zingg can match on tokens as effectively as on raw values when the tokenization is consistent. + +{% hint style="success" icon="right-long" %} +**Read more**: For the full match type reference → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +{% endhint %} + +### GDPR and CCPA considerations + +Zingg is a processing tool. GDPR and CCPA compliance obligations apply to how you use Zingg output, not to Zingg itself. Specific considerations: + +* **Right to erasure**: If a subject requests deletion, identify their Zingg ID in the output and delete all records associated with that cluster from your output store. Zingg Enterprise's persistent Zingg IDs make the process easier to implement consistently. +* **Data minimization**: Configure only the fields you need for matching in your field definitions. Fields not needed for entity resolution can be excluded from Zingg processing. +* **Purpose limitation**: Zingg output should only be used for the entity resolution purpose stated in your data processing agreements. + +{% hint style="success" icon="right-long" %} +**Read more**: For GDPR and CCPA identity resolution use cases: + +* [GDPR use case on identity resolution](https://zingg.ai/product/entity-resolution-solutions/gdpr) +* [CCPA use case on identity resolution](https://zingg.ai/product/entity-resolution-solutions/ccpa) +{% endhint %} diff --git a/docs/security-and-privacy/telemetry-and-usage-metrics.md b/docs/security-and-privacy/telemetry-and-usage-metrics.md new file mode 100644 index 000000000..d227f769b --- /dev/null +++ b/docs/security-and-privacy/telemetry-and-usage-metrics.md @@ -0,0 +1,71 @@ +--- +description: >- + Zingg captures a small set of runtime metrics to help improve the product. No + input data, output data, or user data is ever captured or transmitted. +--- + +# Telemetry and Usage Metrics + +Zingg captures a small set of anonymous runtime metrics like execution time, Zingg version, Java version, model ID, and record count, to help the team understand how Zingg is used in real deployments and prioritise improvements. + +The telemetry is intentionally minimal. No field values, no record content, no personally identifiable information, and no user data leaves your environment. If you prefer not to send any telemetry at all, it can be disabled with a single config setting. + +{% hint style="success" icon="right-long" %} +Telemetry events are sent to Zingg servers when `collectMetrics` is `true` (the default). Setting `collectMetrics` to `false` logs a blank event and prevents any data from being sent. +{% endhint %} + +### **What is captured** + +
MetricDescription
Data source typeFormat of your data source (e.g. CSV, Snowflake, Parquet)
Fields countNumber of fields configured for matching
Record countTotal number of records passing through Zingg
Execution timeDuration of the phase run
Running phaseWhich Zingg phase was executed
Matches and non-matchesCount of matched and non-matched records in the model
JDK informationJDK version and flavour
OS informationOperating system type
Zingg versionVersion of Zingg being used
+ +No field values, record content, or personally identifiable information is included in any telemetry event. + +**Disabling telemetry** + +Set `collectMetrics` to `false` in your config: + +**Python API** + +```python +args.setCollectMetrics(False) +``` + +**JSON config** + +```json +{ + "collectMetrics": false +} +``` + +When `collectMetrics` is `false`, a blank event is logged and no data is sent to Zingg servers. + +**Viewing what is captured** + +To see exactly what telemetry data is logged during a run, edit `log4j2.properties` and set the logging level of `zingg_analytics` to `warn` . + +```bash +logger.zingg_analytics.level=warn +``` + +An example telemetry event (when `collectMetrics` is `true`). + +```json +{ + "client_id": "localhost", + "events": [ + { + "name": "match", + "params": { + "executionTime": "1.743246748E9", + "zingg_version": "0.5.0", + "modelId": "100", + "domain": "localhost", + "java_version": "11.0.18", + "dataCount": "65.0" + } + } + ], + "user_id": "zingg" +} +``` diff --git a/docs/tuning/configure-field-standardization.md b/docs/tuning/configure-field-standardization.md new file mode 100644 index 000000000..953de9741 --- /dev/null +++ b/docs/tuning/configure-field-standardization.md @@ -0,0 +1,135 @@ +--- +description: >- + Normalise field values in your Zingg match output to a canonical form using + predefined value mappings. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# Configure Field Standardization + +{% hint style="info" icon="right-long" %} +**Enterprise** only. The Standardise Postprocessor is available in ZinggEC (Enterprise) and ZinggES (Enterprise Plus). Not available in Community. +{% endhint %} + +After matching, field values across resolved records often contain inconsistent variations. For example, a job title field may contain `VP of Ops`, `Vice President Operations`, and `V.P. Operations` , all representing the same role but appearing differently across source systems. + +The standardized postprocessor normalizes these to a canonical form before writing output. This is the final step between matched data and production-ready golden records. + +### Mapping file format and rules + +Create a JSON file where each element is an array of equivalent values. The first value in each array is the canonical value. All other values in that array map to it in the output. + +**Example: `jobtitles.json`** + +```json +[ + ["Chief Executive Officer", "CEO", + "Executive Director", + "Managing Director", "President"], + ["Vice President of Operations", + "VP of Ops", + "Vice President Operations", + "V.P. Operations", "VP Ops"], + ["Software Engineer", "SWE", + "Software Developer", + "Developer", "Programmer"], + ["Data Scientist", + "Data Science Engineer", + "DS Engineer", "ML Engineer"], + ["Senior Manager", "Sr Manager", + "Sr. Manager", "Senior Mgr"] +] +``` + +#### **Three rules your mapping file must follow** + +
RuleWhy it matters
First value is canonicalAll variants in the array map to the first value in the output. ["Chief Executive Officer", "CEO"] → output will always say "Chief Executive Officer". Order your arrays with the canonical form first.
Lookup is case-insensitive"CEO", "ceo", and "Ceo" all match the same entry. You do not need separate entries for casing variants.
Rows must be disjointA string must not appear in two different arrays. If "CEO" appears in two separate canonical groups, Zingg's behaviour is undefined. Each value belongs to exactly one canonical group.
+ +{% hint style="info" icon="right-long" %} +Place your mapping JSON file in the directory from which you are executing Zingg. Reference it by base filename (without the `.json` extension) in your configuration. For example, a file named `jobtitles.json` is referenced as `jobtitles`. +{% endhint %} + +{% tabs %} +{% tab title="Enterprise" %} +### Configure postprocessing + +Use `STANDARDISE_` to reference a mapping file named `.json`. The `STANDARDISE` prefix tells Zingg which postprocessor type to use, and the basename without `.json` is the filename of your mapping file. For the example mapping above, the file is `jobtitles.json` and the reference is `STANDARDISE_jobtitles`. + +#### Python + +```python +from zingg.client import * +from zinggEC.enterprise.common.EArguments import * +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition +from zinggEC.enterprise.common.StandardisePostprocessorType import StandardisePostprocessorType +from zinggEC.enterprise.common.epipes import * +from zinggES.enterprise.spark.ESparkClient import * +from zinggEC.enterprise.common.EClientOptions import * + +args = EArguments() + +id = EFieldDefinition("id", "string", MatchType.DONT_USE) +id.setPrimaryKey(True) + +job_title = EFieldDefinition("job_title", "string", MatchType.FUZZY) +job_title.setPostProcessors([ + StandardisePostprocessorType("STANDARDISE", "jobtitles") +]) + +fieldDefs = [id, job_title] +args.setFieldDefinition(fieldDefs) +``` + +Configure input and output pipes, then run `match` or `runIncremental` as usual. + +#### JSON + +```json +{ + "fieldDefinition": [ + { + "fieldName": "id", + "matchType": "dont_use", + "dataType": "string", + "primaryKey": true + }, + { + "fieldName": "job_title", + "matchType": "fuzzy", + "dataType": "string", + "postProcessors": "STANDARDISE_jobtitles" + } + ] +} +``` + +{% hint style="info" icon="right-long" %} +Use `STANDARDISE_` to reference a mapping file named `.json`. The `STANDARDISE` prefix tells Zingg which postprocessor type to use. The `` (without `.json`) is the filename of your mapping file. For the example above, the file is `jobtitles.json` and the reference is `STANDARDISE_jobtitles`. +{% endhint %} +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +**CONTENT FOR THIS SECTION TO BE PROVIDED BY SONAL LATER** +{% endtab %} +{% endtabs %} + +### **Verify the standardisation in your output** + +After running `match`, inspect a field that has the postprocessor configured. Values previously appearing as "CEO" or "Executive Director" in your input should now appear as "Chief Executive Officer" in the output, matching the canonical value in your mapping file. + +If the output still shows raw variants: + +* Confirm the mapping file is in the correct directory (the one from which Zingg is being executed) +* Confirm the `postProcessors` JSON value or the `StandardisePostprocessorType` Python argument exactly matches `STANDARDISE_` or `("STANDARDISE", "")` +* Confirm the field is not set as `primaryKey: true` - primary keys are never postprocessed + +{% hint style="success" icon="right-long" %} +**Read more**: + +* [Standardize Fields and Results](../zingg-concepts/how-zingg-learns/zingg-models/standardize-fields-and-results.md) - concept and where it is useful +* [Configure Zingg](../running-zingg/configure-zingg.md) - full field definition configuration including all `EFieldDefinition` methods +* [Match Types](../zingg-concepts/how-zingg-learns/match-types/) - for the MAPPING match type, which uses a similar mapping file approach for matching +{% endhint %} diff --git a/docs/tuning/custom-blocking-and-similarity.md b/docs/tuning/custom-blocking-and-similarity.md new file mode 100644 index 000000000..2b84ea47e --- /dev/null +++ b/docs/tuning/custom-blocking-and-similarity.md @@ -0,0 +1,68 @@ +--- +description: >- + Define your own blocking and similarity functions to replace or extend Zingg's + built-in approaches for advanced accuracy tuning. +--- + +# Custom Blocking and Similarity + +Zingg's built-in blocking and similarity functions handle most entity resolution use cases. For specialized data, such as company names with legal suffixes, phonetically similar names, or\ +domain-specific identifiers; you can define your own functions. + +Custom blocking functions control which records are compared. Custom similarity functions control how two field values are scored when they are compared. + +### Custom blocking functions + +Zingg evaluates custom blocking functions when building the blocking tree. The blocking tree works on the matched records you provided during labeling; at every node, Zingg selects the hash function and the field that produces the least elimination of your known matching pairs. + +**What makes a good blocking function:** + +A good blocking function never eliminates a matching pair entirely. It groups records that could be the same entity, even imperfectly; so the similarity model can evaluate them. A poor blocking function eliminates matching pairs from comparison entirely; no similarity scoring happens after that point. + +**Example: evaluating `first1char` on `firstname`** + +Take two labeled matching pairs: + +| 1 | A | john | j | +| - | - | ----- | - | +| 1 | B | johnh | j | +| 2 | A | mary | m | +| 2 | B | marry | m | + +Both pairs produce the same output from `first1char` - no elimination. This is a good function for `firstname`. + +**Contrast: `last1char` on `firstname`** + +| **1** | A | john | n | +| ----- | - | ----- | - | +| 1 | B | johnh | h | +| 1 | A | mary | y | +| 2 | B | marry | y | + +Pair 1 is eliminated (`n` ≠ `h`). `last1char` is not a good function for `firstname`. Zingg will therefore not choose it. + +So `first1char(firstname)` will be selected. It brings near-similar records together - clustering them to break the cartesian join. + +#### Registering custom blocking functions + +Business-specific blocking functions must be added to two places: + +1. **`HashFunctionRegistry`** - registers the function so Zingg can find it +2. **hash functions config** - tells Zingg which functions are available to evaluate + +Zingg evaluates all registered functions against your training data and selects the best combination automatically. + +{% hint style="success" icon="right-long" %} +Custom blocking functions apply to both Community and Enterprise editions. The function evaluation happens during `train` . You do not need to re-run `findTrainingData` or `label` . +{% endhint %} + +#### Custom similarity functions + +For similarity, you can define your comparison measures alongside Zingg's built-in ones. Each `dataType` has predefined features—for example, the `string` type with `FUZZY` match is configured for Affine and Jaro-Winkler string comparison. + +You can define your comparison functions and register them so Zingg uses them as additional features for the classifier. The classifier then determines the best weight for each feature, including your custom one, based on your labeled training data. + +_**LINK TO BE ADDED - GitHub Zingg repository custom similarity example (add link to**** ****`github.com/zinggAI/zingg`**** ****once confirmed by team)**_ + +Custom similarity and blocking function implementation requires Java/Scala code changes to the Zingg JAR. These are advanced customizations. For most use cases, adjusting match types, stopwords, and training data is sufficient before reaching this step. + diff --git a/docs/tuning/improve-accuracy/README.md b/docs/tuning/improve-accuracy/README.md new file mode 100644 index 000000000..2e31942f4 --- /dev/null +++ b/docs/tuning/improve-accuracy/README.md @@ -0,0 +1,60 @@ +--- +description: >- + Approaches for tuning your Zingg model when match results are not accurate + enough. +--- + +# Improve Accuracy + +When your Zingg match results contain false positives (records incorrectly grouped together) or false negatives (matching records not found), there are several ways to improve accuracy\ +without starting over. + +Start with the approaches in this order; each is less effort than the next. + +### Step 1: Add more training data + +The most reliable way to improve accuracy is to label more pairs. Return to the `label` phase, run `findTrainingData` again, and focus on labeling the types of pairs that are causing problems. + +* If you have too many false positives (records incorrectly merged), add more non-match labels for pairs that look similar but are different entities. +* If you have too many false negatives (matches being missed), add more match labels for pairs that look different but are the same entity. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* Label training pairs - [Label training pairs](../../running-zingg/label-training-pairs.md) +* Create training data - [Create training data ](../../running-zingg/create-training-data.md) +{% endhint %} + +### Step 2: Check your field match types + +Review your `FieldDefinition` match types. Common mismatches: + +* Using `FUZZY` for a field that should be `EXACT` - for example, SSN or national ID numbers should always use EXACT. Using FUZZY allows near matches that produce false positives. +* Using `EXACT` for a field that has variations - for example, address fields almost always have variations and should use FUZZY. +* Using `DONT_USE` for a field that actually carries strong identity signal - adding it back with the right match type can improve recall + +{% hint style="success" icon="right-long" %} +**Read more**: Match types reference - [Match types](../../zingg-concepts/how-zingg-learns/match-types/) +{% endhint %} + +### Step 3: Remove stopwords + +Common words like Mr, St, Street, Pvt add noise to fuzzy matching on address and name fields. Running the stopwords removal step reduces this noise and can improve both precision and recall. + +{% hint style="success" icon="right-long" %} +**Read more**: Remove stopwords - [Remove stopwords](remove-stopwords-optional.md) +{% endhint %} + +### Step 4: Verify blocking + +If Zingg is missing matches that you know exist (false negatives), the blocking model may be filtering them out before they even reach the similarity model. Run Verify Blocking to inspect which pairs are being blocked and identify coverage gaps. + +{% hint style="success" icon="right-long" %} +**Read more**: Verify blocking - [Verify blocking](../../running-zingg/verify-blocking.md) +{% endhint %} + +### Step 5: Custom blocking and similarity + +For advanced use cases, you can define your own blocking and similarity functions to replace\ +or extend Zingg's built-in approaches. See [Custom Blocking and Similarity](../custom-blocking-and-similarity.md) for advanced tuning approaches. + diff --git a/docs/tuning/improve-accuracy/remove-stopwords-optional.md b/docs/tuning/improve-accuracy/remove-stopwords-optional.md new file mode 100644 index 000000000..c71ba7b21 --- /dev/null +++ b/docs/tuning/improve-accuracy/remove-stopwords-optional.md @@ -0,0 +1,86 @@ +--- +description: >- + An optional pre-processing step that improves matching accuracy by excluding + high-frequency words that carry no matching signal. +--- + +# Remove Stopwords (Optional) + +{% hint style="success" icon="right-long" %} +This is an optional step. Run this between [Configure Zingg](../../running-zingg/configure-zingg.md) and [Create Training Data](../../running-zingg/create-training-data.md) for best results. You can run it after matching has already started, but you will need to re-run the match phase for the stopwords to take effect. Most users skip this on the first run. +{% endhint %} + +Common words like `Mr`, `Pvt`, `Av`, `St`, `Street` do not add differential signals and can confuse matching. These are called stopwords. Zingg can recommend which words to treat as stopwords by analyzing your data before training begins. + +This step is optional but recommended for address, company name, and location\ +fields where generic terms are common. + +{% tabs %} +{% tab title="Community" %} +### **Step 1: Generate stopword recommendations** + +#### **Python** + +```python +options = ClientOptions([ ClientOptions.PHASE, "recommend" ]) +zingg = Zingg(args, options) +zingg.setColumn("") +zingg.initAndExecute() +``` + +#### **CLI** + +```bash +./scripts/zingg.sh --phase recommend --conf config.json --column +``` + +{% hint style="success" icon="right-long" %} +Stopwords are stored at: `models/100/stopWords/columnName`. The output gives you the list of stopwords along with their frequency. +{% endhint %} + +### **Step 2: Review the recommendations** + +By default, Zingg extracts 10% of the high-frequency unique words from your dataset. To change this, set `stopWordsCutoff` in your config file under the relevant field: `"stopWordsCutoff": 0.1`. + +### **Step 3: Configure stopwords in your field definition** + +Once you have verified the stopwords, add the `stopWords` path to the relevant field in your config. The CSV file must have one word per row and include a header row - Zingg ignores the header by default. + +#### **JSON** + +```json +{ + "fieldDefinition": [{ + "fieldName": "fname", + "matchType": "fuzzy", + "fields": "fname", + "dataType": "string", + "stopWords": "models/100/stopWords/fname.csv" + }] +} +``` +{% endtab %} + +{% tab title="Enterprise" %} +### **Python** + +```python +options = ClientOptions([ ClientOptions.PHASE, "recommend" ]) +zingg = EZingg(args, options) +zingg.setColumn("") +zingg.initAndExecute() +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase recommend --conf config.json --column +``` +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +Stopwords are stored as a table: `zingg_stopWords_columnName_modelId` + +**CHECK WITH SONAL ABOUT THIS TOPIC - NEEDS ENTIRELY DIFFERENT SET OF CONTENT TO BE DISCUSSED LATER.** +{% endtab %} +{% endtabs %} diff --git a/docs/zingg-concepts/community-vs-enterprise/README.md b/docs/zingg-concepts/community-vs-enterprise/README.md new file mode 100644 index 000000000..ca61784e5 --- /dev/null +++ b/docs/zingg-concepts/community-vs-enterprise/README.md @@ -0,0 +1,91 @@ +--- +description: >- + Two paths, one engine. Choose based on where you are in your entity resolution + journey. +--- + +# Community vs Enterprise + +Every Zingg edition is built on the same foundation: probabilistic matching, active learning, and warehouse-native execution. Enterprise takes that further delivering faster, more accurate entity resolution with deterministic rules, explainability, and full model lifecycle control + +What changes is the identity infrastructure, operational control, and advanced matching capability you obtain on top of it. + +Community is the right place to start. Enterprise is where you go when identity resolution moves into production and needs to stay accurate, auditable, and stable over time. + +
CommunityEnterprise LiteEnterprise
Who it is forTeams that are evaluating Zingg, running proofs of concept, or operating on self-managed Spark infrastructure.Teams that are moving entity resolution into production and need persistent entity IDs and incremental updates.Teams who need full lifecycle control: seamless model upgrades, governance, explainability, and dictionary-based matching for complex data.
Identity graphZ Cluster is a unique cluster identifier assigned per run.Persistent Zingg ID (GUID). Stable across runs and safe to reference in CRMs, warehouses, and pipelines.Persistent Zingg ID plus seamless ID reassignment when you upgrade your model. Downstream systems stay intact across changes.
Matching capabilityProbabilistic matching trained on your labeled pairs. Handles typos, abbreviations, and variations across any field type.Probabilistic + deterministic matching in a single flow. Hard rules for trusted identifiers like SSN, tax ID, email. Pass Through for records that should appear in output but not influence matching.Everything in Lite, plus dictionary-based matching (nicknames, aliases, company name variants). Standardize fields before and after matching. Match statistics and explainability for audit and governance.
Model operationsTrain once and match as data arrives.Incremental runs update the identity graph as new records arrive without retraining. Verify blocking before committing to training.Everything in Lite, plus compare two trained models side by side before deploying. Upgrade models without disrupting Zingg IDs. Re-assign IDs from an old model to a new one.
PlatformAny Spark environment - Databricks, Fabric, EMR, GCP Dataproc, and local Spark.All Community platforms, plus native Snowflake run (no Spark cluster required), Unity Catalog, and OneLake integration.All platforms in Lite.
+ +### When to choose each edition + +_Start with Community._ + +You get the full probabilistic matching engine, active learning, and support for every major Spark platform. Community runs on Databricks, Fabric, EMR, GCP Dataproc, or a local Docker container. + +You can match any entity type, like customers, patients, suppliers, products, and citizens, on your own data with no time limit and no cost. + +_The limitation is operational:_ Community's `Z Cluster` is non-persistent. If you re-run the match job, cluster IDs may change. You cannot safely store them in downstream systems. That is the boundary where Community ends and Enterprise begins. + +
+ +We need to put entity resolution into production and keep it running reliably + +_Choose Enterprise Lite_. + +The step from Community to Enterprise Lite is a step from evaluation to production. The core change is the `Zingg ID`: a globally unique, persistent `GUID` assigned to each resolved entity. It does not change between runs. Your CRM, data warehouse, and downstream pipelines can store it with confidence. + +Enterprise Lite also gives you: + +* [**Incremental matching**](../../running-zingg/run-incremental-matching.md) **-** update the identity graph as new records arrive, without re-running on your entire dataset +* [**Deterministic matching**](../entity-resolution/deterministic-vs-probabilistic-matching.md) **-** combine hard rules for known trusted identifiers (SSN, tax ID, email) with probabilistic ML in a single flow +* [**Pass Through**](../pass-through.md) **-** records that are incomplete or unsuitable for matching still appear in your output and receive a Zingg ID for governance and compliance +* [**Production-grade notebooks**](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/platform-guides) **-** a 7-notebook sequence, one per workflow phase, built for repeatable production runs + +Clients who move to Enterprise Lite are typically building Customer 360 views, patient master indexes, or supplier deduplication pipelines where the entity identifier needs to be stable and trusted downstream. + +
+ +
+ +We need to tune our model over time and handle complex data patterns + +_Choose Enterprise._ + +Enterprise is built for teams where entity resolution is a core data product, not a one-time project. + +The additional capabilities in Enterprise address two things: complex data patterns and model lifecycle control. + +**Complex data patterns**: + +* Dictionary-based matching - match records where the same person appears under a nickname, short form, or alias (Jon / Jonathan, IBM / International Business Machines) using a user-supplied lookup file +* Standardise fields before matching - normalize address formats, name casing, and code fields before the match runs, for higher accuracy +* Standardize output - normalize field values in resolved output to a canonical form for clean golden records + +**Model lifecycle control**: + +* Compare model outputs - benchmark two trained models against each other before deciding which to deploy. See exactly which clusters changed, merged, or split. +* Re-assign Zingg IDs - when you upgrade your model, Zingg carries over existing IDs to the new model. Downstream systems stay intact. No ID disruption. +* Explainability - see which fields and scores drove each match decision for audit, governance, and stakeholder sign-off +* Match statistics - summary, cluster-level, and record-level breakdowns across every run + +Enterprise clients are typically teams who run Zingg as a core data infrastructure component like MDM platforms, composable CDP pipelines, knowledge graphs, or compliance data products where model quality needs to be demonstrable and IDs need to survive model changes. + +
+ +{% hint style="info" icon="right-long" %} +_Community → Enterprise_ is the move you make when you need persistent entity IDs, incremental updates, and matching that stays accurate as your data and models evolve. + +Enterprise Lite is for production with stability. Enterprise is for production with full lifecycle control. + +Ready to move to Enterprise? + +* See [Migrating to Enterprise](migrating-from-community-to-enterprise-content-to-be-added.md) for the full upgrade path +* [Contact Us](https://www.zingg.ai/company/contact/contact) +{% endhint %} + +{% hint style="success" icon="right-long" %} +**Read more**: Every record in Enterprise output receives a `Zingg_ID`, a GUID that persists across runs, incremental updates, and model upgrades. + +This is the foundational difference from Community, where `Z_Cluster` is non-persistent and cannot be safely stored downstream. + +* [Zingg ID](../concept-glossary.md#zingg-id) +* [Z Cluster and Zingg ID](../z-cluster-and-zingg-id.md) +{% endhint %} diff --git a/docs/zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md b/docs/zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md new file mode 100644 index 000000000..189760471 --- /dev/null +++ b/docs/zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md @@ -0,0 +1,8 @@ +--- +description: >- + What changes when you move from Zingg Community to Enterprise imports, class + names, and new features unlocked. +--- + +# Migrating from Community to Enterprise (Content to be Added) + diff --git a/docs/zingg-concepts/concept-glossary.md b/docs/zingg-concepts/concept-glossary.md new file mode 100644 index 000000000..a40317f40 --- /dev/null +++ b/docs/zingg-concepts/concept-glossary.md @@ -0,0 +1,374 @@ +--- +description: >- + Plain-language definitions of every term used across Zingg docs. Click any + term to expand. +--- + +# Concept Glossary + +{% hint style="success" icon="right-long" %} +New to Zingg? Start here. Understanding these terms will make every other page easier to follow. +{% endhint %} + +### How Zingg Matches + +Explore the core concepts behind Zingg's entity resolution process.\ +Learn how Zingg reduces candidate comparisons, learns matching behavior from labeled examples, and groups related records into accurate clusters without handwritten rules. + +
+ +Blocking model + +Blocking group records into candidate buckets before any matching begins. Without it, Zingg would need to compare every record against every other; at 1 million records, that is 500 billion pairs before any field-level comparison runs. The blocking model reduces Zingg's actual comparison space to 0.05–1% of the full problem, making\ +Enterprise-scale entity resolution is computationally feasible. + +{% hint style="success" icon="right-long" %} +**Read more:** [How Zingg learns](../running-zingg/step-by-step-guide.md) | [Verify blocking](../running-zingg/verify-blocking.md) +{% endhint %} + +
+ +
+ +Active learning + +The process by which Zingg builds its matching model from your feedback rather than a pre-labeled dataset. Zingg presents record pairs; you label each as Match, No Match or Can't Say. Because Zingg selects the pairs where it is most uncertain, 30–50 labelled pairs are\ +typically enough to train a high-accuracy model on datasets of 100,000+ records. + +{% hint style="success" icon="right-long" %} +**Read more:** [How Zingg learns](how-zingg-learns/) | [Label training pairs](../running-zingg/label-training-pairs.md) +{% endhint %} + +
+ +
+ +Probabilistic matching + +Zingg's default matching mode. The model learns similarity weights for each field from your labeled pairs and assigns a match score to every candidate pair. Records above the threshold are grouped into a cluster. The threshold is automatically chosen to balance accuracy and recall. Zingg optimizes it so you do not need to tune a cut-off manually. + +Probabilistic matching handles variations in names, addresses, and other messy real-world data that exact rules cannot account for. \ +&#xNAN;_Available in all editions_ + +{% hint style="success" icon="right-long" %} +**Read more:** [Deterministic vs probabilistic matching](entity-resolution/deterministic-vs-probabilistic-matching.md) +{% endhint %} + +
+ +
+ +Deterministic matching + +User-defined hard rules that force a match regardless of the probabilistic score. If the fields you specify all match exactly; for example, tax ID, date of birth, and registered address Zingg treats those records as the same entity without consulting the ML model. + +Both approaches run in a single flow; deterministic rules are evaluated first, and probabilistic matching handles everything else. \ +&#xNAN;_**Enterprise only**_ + +{% hint style="success" icon="right-long" %} +**Read more:** [Deterministic vs probabilistic matching](entity-resolution/deterministic-vs-probabilistic-matching.md) +{% endhint %} + +
+ +
+ +Transitive closure / graph clustering + +The logic that turns pairwise match decisions into complete clusters. If Record A matches B, and B matches C, transitive closure concludes A, B, and C represent the same entity and groups all three together, even if A and C were never directly compared. + +Zingg uses a graph clustering algorithm to apply this logic correctly without creating false chains from weak indirect matches. This is why records in a cluster can appear at varying confidence levels they matched transitively, not always directly. + +{% hint style="success" icon="right-long" %} +**Read more:** [How Zingg learns](how-zingg-learns/) +{% endhint %} + +
+ +### Match Types and Field Definitions + +Configure the fields used for matching and the logic used to compare them.\ +Learn how field definitions, match types, and related settings influence matching accuracy and model performance. + +
+ +Match types + +The similarity function assigned to each field, telling Zingg how to compare values in that field across records. Zingg provides match types for fuzzy comparison, exact match, email, numeric, text, and several specialized types for alphabet-only or null handling. + +Each field receives one match type. The full list of all match types; what they do, when to use each, and examples on [Match Types](how-zingg-learns/match-types/). + +{% hint style="success" icon="right-long" %} +**Read more:** [Match types](concept-glossary.md#match-types) reference | [Configure Zingg](../running-zingg/configure-zingg.md) +{% endhint %} + +
+ +
+ +Field definition + +The configuration object that tells Zingg which fields to use for matching and how to compare them. + +Each field definition has four attributes: `fieldName` (the column name), `fields` (same as `fieldName` for now), `dataType` (string, integer, double, etc.), and `matchType` (the similarity function to apply). + +{% hint style="success" icon="right-long" %} +**Read more:** [Configure Zingg](../running-zingg/configure-zingg.md) +{% endhint %} + +
+ +
+ +Stop words + +Values in a field that appear so frequently across records that they carry no useful signal for matching. + +Common examples in address fields: "Street", "Avenue", "Building", "Floor". In company fields: "LLC", "Ltd", "Corp". Including stop words in matching inflates similarity scores between unrelated records. Zingg's `stopwords` removal phase analyses your data and recommends which values to treat as stop words before training begins. + +{% hint style="success" icon="right-long" %} +**Read more:** [Remove stopwords](../tuning/improve-accuracy/remove-stopwords-optional.md) +{% endhint %} + +
+ +### Identity and Output + +Understand how Zingg represents and manages resolved entities.\ +Learn how identities are assigned, tracked across runs, and made available to downstream systems. + +
+ +Z Cluster + +The cluster identifier used in Zingg Community (Open Source). Records Zingg groups together as the same entity and shares a `Z Cluster` in the output. + +`Z Cluster` is non-persistent; re-running the job may produce different IDs for the same data. It cannot be safely referenced in downstream systems across runs. + +{% hint style="success" icon="right-long" %} +**Read more:** [How Zingg learns](how-zingg-learns/) +{% endhint %} + +
+ +
+ +Zingg ID + +A globally unique, persistent identifier assigned to each resolved entity in Zingg Enterprise. + +Unlike Z Cluster, the Zingg ID does not change between runs. The downstream systems can store and reference it with confidence it will remain stable across incremental updates. _**Enterprise only**_ + +{% hint style="success" icon="right-long" %} +**Read more:** [Z Cluster and Zingg ID](z-cluster-and-zingg-id.md) +{% endhint %} + +
+ +
+ +Identity graph + +Zingg's underlying data structure representing resolved entities and the relationships between their source records. + +* In Community, the graph is non-persistent and rebuilt from scratch each run. +* In Enterprise, it is persistent and updated incrementally: new and changed records are incorporated without a full re-run, and Zingg IDs serve as stable node identifiers within it. + +{% hint style="success" icon="right-long" %} +**Read more:** [Identity graph](identity-graph.md) | [Run incremental matching](../running-zingg/run-incremental-matching.md) +{% endhint %} + +
+ +
+ +Incremental flow + +The process of updating the identity graph with new, changed, or deleted records without re-running the full match across the entire dataset. Zingg incorporates incoming records into existing clusters, handles cluster merges and unmerges automatically, generates new Zingg IDs for records that do not match any existing cluster, and preserves human-approved decisions so they are not overridden. + +Many tools claim incremental capability but cannot maintain match quality and stable IDs simultaneously. This feature is a key Zingg Enterprise differentiator. _Enterprise only_. + +{% hint style="success" icon="right-long" %} +**Read more:** [Run incremental matching](../running-zingg/run-incremental-matching.md) +{% endhint %} + +
+ +
+ +Golden record + +The single, authoritative version of an entity is built by combining the best available data from all matching source records. + +For example, a retail customer appears in an e-commerce platform, a loyalty program, and a support system under slightly different names and addresses. The golden record merges the most complete and reliable field values from each into a single trusted profile. Zingg identifies which records belong together and the golden record is typically constructed downstream in a data platform or MDM layer that consumes them. + +{% hint style="success" icon="right-long" %} +**Read more:** [What is Zingg](../) | [Community vs Enterprise](community-vs-enterprise/). +{% endhint %} + +
+ +### Zingg Phases + +Understand the phases that make up the Zingg workflow.\ +Learn how operations such as training, matching, and model management work together to resolve entities from raw data. + +
+ +findTrainingData + +Scans your dataset and selects the most informative candidate record pairs for labeling - edge cases where the model has the most to learn. Candidate pairs are written to `UNMARKED_DIR`. + +
+ +
+ +label + +Loads the candidate pairs from `findTrainingData` and presents them in a labeling widget. You mark each pair as Match, No Match, or Uncertain. Labels are saved to `MARKED_DIR`. + +
+ +
+ +train + +Builds the blocking and similarity models from your labeled pairs. Both models are persisted to `zinggDir/modelId`. Once trained, the model can be reused on new data without retraining. + +
+ +
+ +match + +Applies the trained model to your full dataset and writes resolved clusters to the output location. This is the phase that produces deduplicated, entity-resolved output for a single dataset. + +
+ +
+ +link + +Like `match`, but for linking records across two or more separate datasets. Each output record carries a `Z_SOURCE` column indicating which source dataset it came from. Use when you want to find the same entity across datasets without merging them into one. + +
+ +
+ +runIncremental (Enterprise) + +Updates the identity graph with new or changed records without retraining the model. Records that match an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. + +
+ +
+ +explain (Enterprise) + +Shows how a specific cluster was formed, which record pairs were compared, their similarity scores, and how transitive matching connected records through intermediate pairs. Used for governance, audit, and stakeholder review. + +
+ +
+ +generateDocs + +Produces HTML documentation of your labeled training pairs and model statistics. Useful for sharing with subject matter experts before training, or for audit and governance after. + +
+ +
+ +verifyBlocking + +Reports what percentage of your known matching pairs are being blocked together correctly. Run after labeling and before training to catch blocking issues early. + +
+ +
+ +stopwords recommend + +Analyses a specified column and returns a list of high-frequency words that should be treated as stopwords. Optional phase used before training when address or company name fields contain repeated noise words like 'St', 'Ave', 'Inc', 'Ltd'. + +
+ +### Data and Configuration + +Learn how data is prepared and configured for matching.\ +Explore the settings, inputs, and preprocessing concepts that determine how records enter and move through the matching pipeline. + +
+ +Pipes + +Zingg's abstraction for connecting to data. A pipe encapsulates the source or destination of records; a CSV file path, a Snowflake table, a BigQuery dataset, a Cassandra keyspace. `CsvPipe`, `SnowflakePipe`, `BigQueryPipe`, and the generic `Pipe` class are the main pipe types in Community. Enterprise adds `ECsvPipe` and `UCPipe`. + +{% hint style="success" icon="right-long" %} +**Read more**: [Pipes and Data Connections](../connect-your-data/pipes-and-data-connections.md) +{% endhint %} + +
+ +
+ +Pass Through + +An Enterprise feature for records that are partially populated, outdated, or not suitable for matching, but which still need to appear in the output for governance or compliance purposes. `Pass Through` records are excluded from matching, linking, and incremental processes. Each pass through the record receives its own unique `Zingg ID` in the output. + +_Common uses_: Records flagged as deceased, bot-generated accounts identifiable by a synthetic email pattern, or corrupted records that must be traceable but should not influence cluster formation. + +{% hint style="success" icon="right-long" %} +**Read more:** [Pass Through](pass-through.md) +{% endhint %} + +
+ +### Platform and Runtime + +Understand the platform components and runtime environment that power Zingg.\ +Learn how execution settings, deployment options, and infrastructure components affect system behavior and performance. + +
+ +Spark cluster + +The distributed compute environment that runs Zingg's `matching` pipeline is described here. Spark can be self-managed (Apache Spark installed on your own machine or server) or provided as a managed service (Azure Databricks, Microsoft Fabric, AWS EMR, AWS Glue, GCP Dataproc, and Azure Synapse Analytics). + +All of these are different ways to get Spark; Zingg runs on all of them. Zingg Enterprise\ +also supports Snowflake native compute, which requires no Spark cluster at all. + +{% hint style="success" icon="right-long" %} +**Read more:** [Install Zingg](../running-zingg/install-zingg.md) | [Run on cloud](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/platform-guides) +{% endhint %} + +
+ +
+ +Cloud infrastructure vs data platform + +Two things that are easy to conflate but mean different things in Zingg's context. Cloud infrastructure is where computation happens, such as the Spark service or Snowflake compute that processes your data. A data platform is where your data lives -Snowflake.\ +Databricks, BigQuery, Redshift, or a file store like S3. + +These are independent: you can have your data in Snowflake but run Zingg's computation on Spark. Zingg connects to the data platform and runs computations on whichever engine you configure. + +{% hint style="success" icon="right-long" %} +**Read more:** [Connect data](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/connect-your-data) | [Install Zingg](../running-zingg/install-zingg.md) +{% endhint %} + +
+ +
+ +Python API vs CLI + +The two ways to invoke Zingg. The Python API is the most widely adopted method; you call the Zingg phases as Python functions from a notebook or script. The CLI invokes Zingg phases from the command line using a JSON config file. + +Both produce identical results; the choice is a developer preference based on your workflow. The Python API is the default method documented throughout these docs. + +{% hint style="success" icon="right-long" %} +**Read more:** [Python API](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/zingg-python-api) | [Zingg command line](../reference/zingg-command-line.md) +{% endhint %} + +
diff --git a/docs/zingg-concepts/entity-resolution/README.md b/docs/zingg-concepts/entity-resolution/README.md new file mode 100644 index 000000000..9b7d8d23c --- /dev/null +++ b/docs/zingg-concepts/entity-resolution/README.md @@ -0,0 +1,223 @@ +--- +description: >- + What entity resolution is, why the problem is hard to solve at scale, and how + Zingg handles each part of it systematically. +--- + +# Entity Resolution + +Entity resolution is the process of identifying that multiple records across different systems represent the same real-world entity. + +It answers one question: Given fragmented, inconsistent data spread across sales,\ +marketing, support, and operations systems, whose records actually belong to the same\ +customer, supplier, patient, or product? + +This question is harder than it looks. The data does not tell you. The records do not share a common key. The same person appears as "Jon Smith" in your CRM, "Jonathan Smith" in your billing system, and "J. Smith" with a different address in your support platform. No field is wrong. No field is complete. And your downstream systems like analytics, compliance, and AI agents are making decisions on all three records as if they were three different people. + +### Why this problem exists + +Fragmented entity data is a structural consequence of how enterprise software is built. Each application owns its own records and its own identity model. There is no cross-system agreement on how to represent a person, a company, or a location. + +Four forces drive the fragmentation: + +* **Systems are built independently:** CRMs, billing platforms, ERP systems, and support tools each have their own customer or entity schema. A customer acquired through a marketing campaign enters the CRM. The same customer's invoice enters the billing system. Their support ticket enters a helpdesk. No system knows about the others. Each creates its own record. +* **Data entry is inconsistent:** Name abbreviations, spelling variations, transposed date formats, address shorthand—these are not data quality failures. They are the natural output of humans entering data under time pressure across different interfaces. "IBM" and "International Business Machines" are the same company. "Dr. A. Sharma" and "Anita Sharma" may be the same person. No rule-based system can enumerate all the ways real data varies. +* **Records change over time:** People move. Companies merge. Names change after marriage or acquisition. A customer record that was accurate two years ago now has a different address, a different email, and a different phone number—and it still needs to resolve to the same entity in your identity graph. +* **Scale makes manual resolution impossible:** At a few thousand records, you can resolve entities manually or with simple rules. With one million records, naive comparison necessitates evaluating 500 billion record pairs before making a single match decision. At ten million records, the number is 50 trillion.\ + No rules engine can scale to this. No team can review it manually. The problem requires a system that learns and one that reduces the comparison space before it starts. + +### Why custom matching logic breaks down + +Rule-based entity resolution fails for three reasons: + +1. **Rules cannot enumerate variation**. The number of ways a name, address, or company identifier can vary is unbounded. For every rule you write, new variations appear in production data that the rule does not cover. Maintaining a ruleset for a live dataset is a permanent, open-ended engineering commitment. +2. **Rules cannot score confidence**. A rule fires, or it does not. It cannot tell you that two records are probably the same entity or that a cluster has a weak link worth human review. Entity resolution at production scale requires a graded confidence signal; not a binary match/no-match. +3. **Rules do not scale to the comparison space**. At one million records, the naive approach requires evaluating 500 billion record pairs. At ten million, it is 50 trillion. A rule engine applied to every pair is computationally impossible. + +Zingg's ML model solves all three: + +* It learns variation from your data - 30 to 50 labeled examples are enough to build a model that generalizes to patterns it has not seen before. +* It produces a graded confidence score (`Z_MINSCORE` and `Z_MAXSCORE`) per cluster, so you can route high-confidence matches to automated processing and low-confidence matches to human review. +* It uses a blocking model to cut down the comparison space from billions of pairs to a tiny fraction without losing recall, so the similarity model only checks candidate pairs that could plausibly match. + +### How Zingg solves it + +Zingg is an ML-powered entity resolution engine built to run where your data already lives directly on your warehouse or lakehouse, with no data movement and no rules to write or maintain. + +Three capabilities work together to handle the full problem: + +
Warehouse-native executionProbabilistic + deterministic matchingPersistent identity graph
Zingg runs inside Databricks, Microsoft
Fabric, Snowflake, GCP Dataproc, AWS Glue, and AWS EMR. Your data never
leaves your environment. No ETL pipelines. No external APIs. There is no separate infrastructure to operate. The same model that runs on 100,000 records scales to hundreds of millions using your existing Spark or Snowflake compute, without any architectural changes.

Probabilistic matching is Zingg's default. The ML model learns from your labeled pairs; 30 to 50 examples are enough to build a model calibrated
to your specific data and scores every candidate pair on multiple field-level features. It handles
typos, abbreviations, missing values, and format variations automatically.

Deterministic matching (Enterprise) adds hard rules for trusted identifiers.
When two records share the same SSN, tax ID, or email, Zingg treats them
as the same entity without consulting the ML model. Both approaches run in a single flow.

Deterministic vs Probabilistic
Matching

Every resolved entity receives a Zingg ID, a globally unique, persistent GUID assigned in Enterprise that remains stable
across runs, incremental updates, and model changes. Community produces a Z Cluster
that is reassigned each run.
Enterprise produces a Zingg ID you can store in downstream systems
with confidence.

The identity graph grows incrementally. New records are matched to existing clusters without rerunning on your full dataset.

Identity Graph
Z Cluster and Zingg ID

+ +This is the combination that makes enterprise-scale entity resolution computationally feasible and practically maintainable. + +When your data contains reliable unique identifiers, a national ID, an email, or a combination like first name plus date of birth Zingg Enterprise can apply deterministic rules on top of the probabilistic model. Pairs that satisfy a deterministic condition are resolved with a score of 1, without consulting the ML model. Pairs that do not satisfy any condition fall through to probabilistic matching as normal. Enterprise runs both in a single flow, so you do not have to choose between them. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* [Deterministic vs Probabilistic Matching](deterministic-vs-probabilistic-matching.md) +* [How Zingg Learns](../how-zingg-learns/) +* [Zingg Models](../how-zingg-learns/zingg-models/) (blocking + similarity) +{% endhint %} + +### Deduplication, linking, and resolving + +These three operations are often confused. They are distinct in scope and complexity. + +**DEDUPLICATION - within one dataset** + +Deduplication identifies that two records within the same system represent the same entity. This is the most constrained version of the problem same schema, same\ +source, single dataset. + +Zingg's `match` phase does just that. You run it against one dataset. Every record that resolves to the same entity gets the same `Z Cluster` or `Zingg ID`. + +**LINKING - across two datasets** + +Linking identifies that a record in dataset A matches a record in dataset B, where each dataset is individually duplicate-free. Linking is required for reference data mastering, enrichment, and dataset joins when exact key matches do not exist. + +Zingg's link phase does this. Same trained model, two input datasets. The output shows which records from each dataset represent the same entity. + +**RESOLVING - across many systems at scale** + +Full entity resolution identifies the same entity across multiple systems, with no shared key, inconsistent formats, partial data, and records that change over time. This is the general problem that Zingg is built to solve. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* [Run the match phase](../../running-zingg/run-the-match-phase.md) +* [Link across datasets](../../running-zingg/link-across-datasets.md) +{% endhint %} + +### Where teams use entity resolution + +Entity resolution is not a customer data problem. It is an entity data problem. Any time a real-world object, a person, organisation, location, product, or asset appears under different\ +representations across systems, entity resolution is the mechanism that unifies it. + +
+ +Customer 360 + +Modern enterprises run on specialised applications, CRM, e-commerce, support,\ +loyalty, and finance. Each system holds a partial view of the customer. + +The goal of Customer 360 is not to delete duplicate records. It is to bring all interactions and data points for a customer together so you can understand the complete journey, enable accurate segmentation, and power downstream AI agents with a unified view. + +Entity resolution is the step that makes such a scenario possible. Without it, every downstream model, campaign, and agent is working from a fragmented picture. + +
+ +
+ +Master Data Management (MDM) + +MDM is the discipline of creating and maintaining a single authoritative record, a golden record for each core business entity: customer, supplier, product, location, and employee. + +Entity resolution is the part of every MDM system that finds out which records from different source systems are for the same entity before the golden record is created. + +Zingg Community is a DIY MDM. You build the golden record layer on top of Zingg's match output in your data store of choice. + +Zingg Enterprise is a warehouse-native MDM that uses `Zingg ID`s, has incremental updates, and supports survivorship, providing the complete identity infrastructure stack without needing a separate MDM platform. + +
+ +
+ +Composable CDPs + +A composable CDP is a customer data platform built from modular components on your existing data stack, rather than a monolithic vendor platform. + +Entity resolution is the identity layer in a composable CDP architecture. Zingg resolves customer identities across all source systems and produces a persistent Zingg ID that becomes the customer identifier for the rest of the stack segmentation, activation,\ +personalization, and AI agents. + +Because Zingg runs inside your warehouse, it fits natively into Databricks, Fabric, or Snowflake-based composable architectures without a separate data movement layer. + +
+ +
+ +Knowledge Bases and Graph RAG + +Large language models and retrieval-augmented generation (RAG) pipelines retrieve knowledge from structured data sources. When those sources contain fragmented, inconsistent entity representations, the retrieval layer returns incomplete or conflicting\ +context, and the LLM gives wrong or incomplete answers. + +Entity resolution is the pre-processing step that unifies entity representations before they reach the knowledge base or vector store. Zingg clusters all records representing the same entity and assigns a single `Zingg ID`. The retrieval layer then finds all of them\ +together, and the LLM reasons over a complete, entity-aware view. + +This process is the Identity RAG pattern entity resolution as infrastructure for AI accuracy. + +{% hint style="success" icon="right-long" %} +**Read more**: [Enhancing LLM applications with Zingg and LangChain](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/recipes-and-integration) +{% endhint %} + +
+ +
+ +Fraud Detection + +Fraudulent actors create multiple accounts or profiles with small variations in identity fields to evade detection different email formats, transposed date of birth, and slight name variations. + +Entity resolution identifies when records that look different actually belong to the same entity, surfacing hidden connections that threshold-based or rule-based systems miss. + +Zingg's probabilistic matching is calibrated to detect these deliberate variations without generating false positives on legitimate name differences. + +
+ +
+ +KYC and AML + +Know Your Customer and Anti-Money Laundering workflows require matching incoming data against existing records, sanctions lists, and watchlists often across inconsistent name formats, transliterations, and missing fields. + +Entity resolution handles these variations systematically, reducing false positives and ensuring compliance teams review the right cases rather than being overwhelmed by noise. + +Zingg Enterprise's deterministic matching adds a hard-rule layer for trusted identifiers alongside probabilistic matching, which is exactly the combination KYC and AML workflows require: fuzzy matching for variations, exact matching for confirmed identifiers. + +
+ +### Frequently Asked Questions + +
+ +Is entity resolution the same as deduplication? + +No, and this distinction matters. Deduplication removes exact or near-exact duplicate records within a single system. It is a special case of entity resolution where the\ +Variations are minimal, and the scope is limited to a single dataset. + +Entity resolution goes further. It identifies records that represent the same real-world entity even when they look significantly different, with variations in spelling, missing fields, different formats and across completely separate systems. + +"John Smith" in the CRM, "J. Smith" in the billing tool, and "Jonathan Smith" in the support platform may be the same person. Deduplication would not catch this. Entity\ +resolution does. + +Zingg's commercial use cases, Customer 360, fraud, KYC, AML, MDM all require full entity resolution. This is why Zingg is positioned around entity resolution and identity\ +resolution, not data deduplication. + +
+ +
+ +Can I do entity resolution with a graph database alone? + +Not if your data has fuzzy variation. Graph databases like Neo4j are excellent at representing and querying relationships between resolved entities. But they rely on trusted, high-quality identifiers like passport IDs, tax numbers, and exact match keys to define edges between records. When your data has typos, abbreviations, missing fields, or format variations, you cannot define those edges reliably without fuzzy matching first. + +Zingg and graph databases work best together. Zingg does entity resolution, which means it finds out which records are for the same entity using ML-based probabilistic and deterministic matching. The graph database is used for downstream relationship analysis and inference in AML, KYC, Knowledge Graph, and Customer 360 scenarios. + +{% hint style="success" icon="right-long" %} +**Read more**: [Connect graph databases](../../connect-your-data/connect-graph-databases-neo4j.md) +{% endhint %} + +
+ +{% hint style="success" icon="right-long" %} +Ready to see entity resolution in action? + +* [Run Zingg on your local machine](../../running-zingg/quick-start-docker.md) - full workflow in 30 minutes +{% endhint %} + +{% hint style="warning" icon="right-long" %} +Need entity resolution at production scale with persistent identity and incremental\ +processing? + +* [Talk to us about Enterprise](https://www.zingg.ai/company/contact/contact) +* [Community vs Enterprise](../community-vs-enterprise/)
+{% endhint %} diff --git a/docs/zingg-concepts/entity-resolution/deterministic-vs-probabilistic-matching.md b/docs/zingg-concepts/entity-resolution/deterministic-vs-probabilistic-matching.md new file mode 100644 index 000000000..84933ddbc --- /dev/null +++ b/docs/zingg-concepts/entity-resolution/deterministic-vs-probabilistic-matching.md @@ -0,0 +1,63 @@ +--- +description: >- + How Zingg's two matching approaches work, when to use each, and how to + configure deterministic rules. +tags: + - ent +--- + +# Deterministic vs Probabilistic Matching + +Zingg supports two matching approaches: probabilistic and deterministic. Both are explained on this page. Zingg Enterprise runs them together in a single flow. Probabilistic handles variations and uncertainty; deterministic handles trusted identifiers that should always produce an exact match. + +### Probabilistic matching + +Probabilistic matching is Zingg's default and is available in all editions. The model learns field-level similarity weights from your labeled training pairs and assigns a match score to every candidate pair. Pairs above the automatically optimized threshold are grouped into a cluster. + +This approach handles the full range of real-world data quality problems: typos, abbreviations, missing fields, name variations, and format differences across systems." + +_You do not write rules. You label examples. Zingg learns the rest._ + +### Deterministic matching + +Deterministic matching lets you define hard rules for cases where certain field combinations should always produce a match, regardless of the probabilistic score. If two records share the same combination of identifiers you specify, Zingg treats that pair as a match with a score of 1, bypassing the ML model entirely for that pair. + +Zingg Enterprise applies deterministic rules first. Pairs resolved deterministically are not re-evaluated probabilistically. Pairs that do not satisfy any deterministic condition fall through to the probabilistic model. Both paths produce output in the same format. + +### Configuring deterministic matching + +{% hint style="info" icon="right-long" %} +Deterministic matching is Enterprise only. Available in Enterprise Lite and above +{% endhint %} + +To configure deterministic matching, define each rule as a `DeterministicMatching` condition in Python or as a `matchCondition` block in JSON, then pass all conditions to `setDeterministicMatchingCondition()`. Full Python and JSON examples → [Configure Zingg](../../running-zingg/configure-zingg.md) + +### How the conditions work + +Each `matchCondition` is evaluated independently. Any pair that satisfies at least one condition is resolved as a match with a score of 1. You can define as many conditions as your data requires. + +Using the example above: + +
ConditionWhat it means
fname + stNo + add1 match exactlySame first name at the same street number and address line → treated as same entity, score 1.
fname + dob + ssn match exactlySame first name, date of birth, and SSN → treated as same entity, score 1.
fname + email match exactlySame first name and email address → treated as same entity, score 1.
+ +
+ +When should I use deterministic matching? + +Use deterministic matching when your data contains reliable unique identifier fields where an exact match between two records should always mean they are the same entity. + +Good candidates for deterministic rules: + +* First name + email address +* First name + date of birth + national ID +* First name + street number + address line 1 + +Do not use deterministic matching on fields that are frequently incomplete or inconsistent. Null values or formatting variations will cause valid matches to be missed. Apply deterministic rules only to fields you trust. + +If you are unsure, start with probabilistic matching only. Add deterministic rules once you have reviewed match output and identified high-confidence identifier combinations in your data. + +{% hint style="success" icon="right-long" %} +**Read more:** [Configure Zingg](../../running-zingg/configure-zingg.md) | [Concepts glossary](../concept-glossary.md) +{% endhint %} + +
diff --git a/docs/zingg-concepts/how-zingg-learns/README.md b/docs/zingg-concepts/how-zingg-learns/README.md new file mode 100644 index 000000000..750bb33b6 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/README.md @@ -0,0 +1,114 @@ +--- +description: >- + How Zingg builds a matching model from your data and why it handles scale, + variation, and uncertainty in ways that rules-based approaches cannot. +--- + +# How Zingg Learns + +{% hint style="success" icon="right-long" %} +**Read more**: + +* For the step-by-step workflow you run to produce match results - [Step by step Guide](../../running-zingg/step-by-step-guide.md) +* For a deep dive on the two models Zingg builds internally - [Zingg Models](zingg-models/) +{% endhint %} + +Most entity resolution ML tools give you two options: write rules by hand or load a pre-trained model and hope it generalizes to your data. + +Zingg does neither. It learns a model from your data, calibrated to your specific field distributions, your specific variations, and your specific understanding of what a match means. + +Zingg starts by scanning your dataset and selecting the most informative candidate pairs, edge cases, near misses, and ambiguous records where human judgment matters most. It does not ask you to label random samples. It is highly selective about which pairs it surfaces, choosing the ones that will teach it the most with the least labeling effort. + +You label those pairs typically 30 to 50, as Match, No Match, or Uncertain. From those labels, Zingg builds two models: a blocking model that cuts down the comparison space to a tiny fraction of all possible pairs, and a similarity model that scores each remaining candidate pair with graded confidence. + +The result is a model that understands your data. Not a generic algorithm applied to it. + +#### The problem Zingg is built to solve and why it is harder than it looks + +Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem.\ +And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. + +
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.

This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.

Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always break

Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.

A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.

Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.

The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.

The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.

Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.

Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely.

Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

The data drift problem - why a model trained once decays

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.

Rule-based systems have no mechanism to detect or adapt to these changes. You rewrite the rules manually when performance degrades, which means that someone has to notice the degradation first.

Zingg Enterprise's incremental flow detects when new records do not fit existing clusters cleanly and surfaces them for review. The compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live.

The incremental new data problem

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.

First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.

Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

+ +### The active learning loop + +Zingg does not require thousands of pre-labeled training examples. It uses an active learning approach: it finds the record pairs where it is most uncertain, presents them to you, and learns from your labels. + +The loop is: + +{% stepper %} +{% step %} +### Step 1: `findTrainingData` + +Zingg scans your dataset and selects a set of candidate record pairs specifically chosen because they are the most uncertain and therefore the most informative for the model to learn from. Not random samples. The pairs are most likely to improve the model. +{% endstep %} + +{% step %} +### Step 2: `label` + +You review each pair and mark it as Match, No Match, or Uncertain. This is the only step in the Zingg workflow that requires human input. No ML knowledge needed; only\ +domain understanding of whether two records represent the same real-world entity. +{% endstep %} + +{% step %} +### Step 3: Repeat steps 1 and 2 + +Run `findTrainingData` and `label` again. Zingg surfaces a new set of candidate pairs; it is still uncertain about after learning from your previous labels. You repeat this loop until the pairs being surfaced align with your expectations. + +For most datasets, 30 to 50 labeled `match` pairs is a good starting point. The goal is not a specific count; it is coverage. Label until every field type in your schema is represented in your training data. +{% endstep %} + +{% step %} +### Step 4: `train` + +Once you are satisfied with your labeled pairs, run train. Zingg builds the blocking model and the similarity model from your labels. This step runs once per training cycle. +{% endstep %} +{% endstepper %} + +{% hint style="success" icon="right-long" %} +`findTrainingData` and `label` run multiple times in a loop before train ever runs. + +`train` runs once, after you have enough labeled pairs. +{% endhint %} + +### Why 30 to 50 labels are enough, and when to do more + +Standard supervised ML requires thousands or tens of thousands of labeled examples to reach\ +production accuracy. Active learning requires far less because the labels are chosen, not random. + +Every pair of Zingg surfaces for labeling is specifically selected because it is uncertain given what the model already knows. Each label you add is maximally informative. There is no noise from irrelevant or redundant examples. + +The practical implication: a well-chosen set of 30 to 50 match labels produces a similarity model that generalizes to your full dataset. + +More labels improve accuracy, but the return diminishes quickly. The more important variable is coverage. `Label` until your training set includes examples of all the variation patterns in your data - different name formats, address abbreviations, missing fields; not until you reach a specific number. + +If match performance needs improvement after your first run, return to the label loop. Focus on the patterns your results show are missing or underperforming. + +### The two models Zingg builds + +When you run `train`, Zingg builds two separate models from your `labeled` pairs. Each solves a different part of the entity resolution problem. + +{% hint style="success" icon="right-long" %} +The blocking model and the similarity model are covered in full on their own pages\ +with task-level detail for inspection, tuning, and debugging. + +* [Blocking model](zingg-models/blocking-model.md) +* [Similarity model](zingg-models/similarity-model.md) +* [Zingg Models](zingg-models/) (parent page) + +The graph algorithm that turns pairwise match decisions into complete clusters is also covered in the Zingg Models section. +{% endhint %} + +### Quick reference on which match type for which field + +When you define your field configuration, you choose a match type for each field. Here is a starting point for the most common field types. + +
Field typeRecommended match type + note
First name, last name,
company name
FUZZY - handles spelling variations
and abbreviations. Use MAPPING (Enterprise) for known
alias or nickname lists.
Email addressEMAIL - matches the portion before the @ only, avoiding mismatches from different domains for the same person.
Date of birth, registration dateEXACT - dates should not have fuzzy tolerance.
Postal code, ZIP codePINCODE - handles common format variants.
Street address (full line)FUZZY - or ONLY_ALPHABETS_FUZZY combined with NUMERIC for the street number as a separate field.
Street number, apartment numberNUMERIC - extracts and compares the number portion only.
National ID, SSN, tax IDEXACT - trusted identifiers should never have fuzzy tolerance. Also consider deterministic matching
(Enterprise) for these fields.
Internal record ID (not used for matching)DONT_USE - appears in output but excluded from comparison.
Fields frequently null across source systemsAdd NULLS_MATCH_NOTHING alongside the main match type.
Product descriptions, free-text notesTEXT - word overlap comparison for longer free-text fields.
+ +{% hint style="success" icon="right-long" %} +**Read more:** + +* For full field definition setup including the `fields`, `dataType`, and `stopWords` attributes -\ + [Configure Zingg](../../running-zingg/configure-zingg.md) +* For the complete match types reference with all 12 types - [Match Types](match-types/) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/README.md b/docs/zingg-concepts/how-zingg-learns/match-types/README.md new file mode 100644 index 000000000..f6d2d2ab1 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/README.md @@ -0,0 +1,108 @@ +--- +description: >- + The 12 similarity functions Zingg supports what each does, when to use it, and + which are Enterprise only. +--- + +# Match Types + +Every field in your Zingg configuration gets a match type assigned to it. The match type tells\ +Zingg which similarity function to apply when comparing values in that field across two records.\ +Choosing the right match type for each field is one of the highest-impact configuration decisions you make as it directly affects matching accuracy. + +Multiple match types can be assigned to a single field, separated by commas. + +
Match TypeWhat it doesData typesBest forEditionExample
FUZZYBroad matching tolerant to typos,
abbreviations and variations. Uses fuzzy string similarity features.
string, integer, long, double, dateNames, addresses, free-text fieldsAll"Jon Smith", "Jonathan Smith", and
"J. Smith" all match the same record.
FUZZY_OPTIMISEDSame accuracy as FUZZY, approximately 2X faster. Lower CPU and memory usage on large datasets. Use this in production when FUZZY accuracy is needed at scalestring, integer, long, double, dateSame as FUZZY - use when dataset
is large and performance matters
Enterprise onlySame matching behaviour as FUZZY. Processes a 10M-record dataset in approximately half the time.
EXACTProvides an exact-match signal to the classifier. A strong indicator when two values align perfectly. Records may still match based on other fields and how training pairs were labeled.string, integer, long, date, booleanCountry codes, boolean flags,
categorical fields where no
variation is expected
All"US" and "US"—strong match signal. "US" and ""us"—no exact signal, but the record pair may still match if other fields score highly.
EMAILMatches only the local part before
the @ - ignores the domain entirely.
stringEmail address fieldsAll"john.smith@gmail.com" and
"john.smith@company.com" — match.
"john.smith@gmail.com" and
"jsmith@gmail.com" — no match.
EMAIL_OPTIMISEDSame as EMAIL but significantly faster
evaluation on large datasets.
stringEmail fields at production scaleEnterprise onlySame behavior as EMAIL.
Use when comparing millions of
email addresses.
PINCODEMatches postal and pin codes across
common format variants (e.g. xxxxx
and xxxxx-xxxx).
stringPostal/ZIP code fieldsAll"94102" and "94102-1234" — match.
"94102" and "94103" — no match.
NUMERICExtracts numbers from strings and
compares how many are the same
across both strings.
stringStreet numbers, apartment numbers,
building numbers
All"42B Main St" and "42 Main Street" —
the number 42 is extracted and matched
from both. Alphabetic variation ignored.
NUMERIC_WITH_UNITSExtracts product codes or numbers
with units (e.g. 16gb, 500ml) and
compares how many are the same.
stringProduct specification fields,
size field
All16GB" and "16 GB DDR4" — the 16 and "GB" tokens match. "16gb" and "32gb" — the units match but the numbers do not.
TEXTCompares overlapping words between two strings. Good for longer descriptive text without many typos.stringProduct descriptions, notes,
long free-text fields
All"Enterprise data management platform"
and "data management platform for
enterprise" — high overlap, likely match.
"Enterprise software" and
"consumer hardware" — low overlap,
likely no match.
ONLY_ALPHABETS_EXACTIgnores all numbers, then requires an exact letter match.stringBuilding names where unit numbers
should be ignored
All"Tower A, Flat 12" and "Tower A, Flat 7"
— "Tower A" matches exactly after
numbers are stripped.
"Tower A" and "Tower B" — no match.
ONLY_ALPHABETS_FUZZYIgnores all numbers, then applies fuzzy matching to alphabetic characters onlystringAddress fields where street names
need fuzzy matching and numbers
are handled separately via NUMERIC
All"St. James Rd" and "Saint James Road" —
alphabetic characters compared fuzzily.
Combine with NUMERIC for the
street number.
ONLY_ALPHABETS_FUZZY_OPTIMISEDSame as ONLY_ALPHABETS_FUZZY but optimised for production scale.stringSame as ONLY_ALPHABETS_FUZZY -use when dataset is largeEnterprise onlySame matching behaviour.
Use for large address datasets.
NULL_OR_BLANKBy default Zingg treats nulls as
matches. Add this alongside another
match type to build an explicit feature for null/blank values so the model can learn their effect.
string, integer, long, date, booleanAny field frequently null or blank
across source systems -combine
with FUZZY or EXACT
AllField A: null, Field B: "John" — with NULL_OR_BLANK added, these are NOT treated as matching. Without it, null matches anything.
DONT_USEAppears in output but no computation
is done. Not shown in the labeller
when showConcise is true.
anyRecord IDs, internal keys,
any field needed in output
but not in matching
AllA customer_id field that must appear
in output for traceability but should
not influence whether two records
are the same entity.
MAPPING_(FILENAME)Maps input field values to canonical
values using a user-supplied lookup file. Handles nicknames, abbreviations,
company name variants, and categorical
normalisation. Matching is tolerant
to common variations defined in the mapping file.
stringName fields (nicknames), company
fields, categorical fields with
different representations across
systems
Enterprise only"Jon", "Jonathan", "Johnny" all map
to canonical "John" via nicknames.json.
"M", "Male", "1" all map to "M"
via gender.json. Matched before
similarity scoring runs.
+ +{% tabs %} +{% tab title="Python" %} +{% code overflow="wrap" %} +```python +from zingg.client import * +from zingg.pipes import * + +#Community +fname = FieldDefinition("fname", "string", MatchType.FUZZY) +lname = FieldDefinition("lname", "string", MatchType.FUZZY) + +stNo = FieldDefinition("stNo", "string", MatchType.FUZZY) +add1 = FieldDefinition("add1", "string", MatchType.FUZZY) + +dob = FieldDefinition("dob", "string", MatchType.EXACT) +ssn = FieldDefinition("ssn", "string", MatchType.EXACT) + +fieldDefs = [ fname, lname, stNo, add1, dob, ssn ] +args.setFieldDefinition(fieldDefs) +``` +{% endcode %} +{% endtab %} + +{% tab title="JSON" %} +```json +{ + "fieldDefinition" : [ + { + "fieldName" : "fname", + "matchType" : "fuzzy", + "fields" : "fname", + "dataType" : "string" + }, + { + "fieldName" : "lname", + "matchType" : "fuzzy", + "fields" : "lname", + "dataType" : "string" + }, + { + "fieldName" : "stNo", + "matchType" : "fuzzy", + "fields" : "stNo", + "dataType" : "string" + }, + { + "fieldName" : "add1", + "matchType" : "fuzzy", + "fields" : "add1", + "dataType" : "string" + }, + { + "fieldName" : "dob", + "matchType" : "exact", + "fields" : "dob", + "dataType" : "string" + }, + { + "fieldName" : "ssn", + "matchType" : "exact", + "fields" : "ssn", + "dataType" : "string" + } + ] +} +``` +{% endtab %} +{% endtabs %} + +
+ +Which match type should I use for each field type? + +A quick reference for the most common fields: + +* **Name fields (first name, last name, company name)** → `FUZZY`. Handles spelling variations and abbreviations. Use MAPPING for known alias or nickname lists (Enterprise only). +* **Email address** → `EMAIL`. Matches before the `@` ; only avoids mismatches from different email domains for the same person. +* **Date of birth / registration date** → `EXACT`. Dates should not have fuzzy tolerance. +* **Postal / ZIP code** → `PINCODE`. Handles common format variants. +* **Street address (full address line)** → `FUZZY` or `ONLY_ALPHABETS_FUZZY` combined with NUMERIC for the street number as a separate field. +* **Street number / apartment number** → `NUMERIC`. Extracts and compares the number portion only. +* **Internal record ID (not used for matching)** → `DONT_USE`. Appears in output but excluded from comparison. +* **Any field frequently null across source systems** → Add `NULL_OR_BLANK` alongside the main match type. +* **Product descriptions / notes** → TEXT. `Word` overlap comparison for longer free-text fields. + +
+ +{% hint style="success" icon="right-long" %} +**Read More:** + +Configure Zingg (full field definition setup in notebook 01) | Match types reference (Section 7) | Concepts glossary +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/dont_use-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/dont_use-match.md new file mode 100644 index 000000000..e7fe2c16a --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/dont_use-match.md @@ -0,0 +1,120 @@ +--- +description: >- + Includes a field in Zingg output but excludes it from matching entirely. No + similarity computation is run on DONT_USE fields. +--- + +# DONT\_USE Match + +### What `DONT_USE` does + +`DONT_USE` tells Zingg to carry a field through to the output without using it in any similarity computation. No matching algorithm is applied. The field appears in every output record exactly as it appeared in the input. + +`DONT_USE` fields are also hidden from the labeller when the `showConcise` flag is set to `true` —\ +so your domain experts see only the fields that are actually being used for matching when they label pairs. + +### How the algorithm works + +_**COMMENT FOR TEAM — Algorithm detail for DONT\_USE to be added here.**_ + +{% hint style="success" icon="right-long" %} +`DONT_USE` performs no computation. There is no similarity algorithm to describe - the field is passed through to output unchanged. +{% endhint %} + +### What **`DONT_USE`** matches and what it does not + +
ScenarioWith DONT_USEWithout DONT_USE
customer_id field in outputAppears in every output row with original value.
Not used in matching.
Would need to be in field
definitions for output -
but would also contribute
to match scoring.
record_id = "REC-00123"Carried to output as "REC-00123". Not compared against other records."REC-00123" would be compared fuzzily or exactly against other record IDs - likely producing false positives or false negatives.
Shown in labellerHidden when showConcise=true
reduces visual noise for labellers.
Would appear in every labeller row, distracting from the fields that actually drive matching.
+ +### When to use `DONT_USE` + +
+ +Record IDs and internal keys + +Any field that uniquely identifies a record in a source system `customer_id`, `account_id`, `case_id`, `transaction_id` should use `DONT_USE`. + +These fields must appear in output so you can trace resolved clusters back to source records. But they should never influence matching - different source systems use different ID schemes for the same entity. + +Using `FUZZY` on a customer\_id field would cause records with similar-looking IDs from different systems to match, which is almost certainly wrong. + +
+ +
+ +Audit and traceability fields + +Timestamps, data source identifiers, record creation dates, batch IDs - any field needed in output for audit or traceability purposes but irrelevant to entity identity. + +
+ +
+ +Reducing labeller noise with showConcise + +When your schema has many fields but only 4-6 are actually used for matching, setting `DONT_USE` on the rest and using `--showConcise=true` in the CLI makes the labelling\ +interface cleaner. Labellers see only the fields that drive decisions. + +
+ +### When not to use `DONT_USE` + +
+ +When the field carries a matching signal + +If a field has information that should influence whether two records represent the same entity, even as a weak signal do not use `DONT_USE`. Use `FUZZY` with low weight, or include it in a deterministic matching condition (Enterprise). + +`DONT_USE` is for fields that carry zero matching information. + +
+ +{% tabs %} +{% tab title="Python" %} +### **Community** + +```python +from zingg.client import * + rec_id = FieldDefinition("rec_id", "string", MatchType.DONT_USE) +``` + +### **Enterprise** + +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + rec_id = EFieldDefinition("rec_id", "string", MatchType.DONT_USE) +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). The CLI command is identical for both editions. +{% endhint %} + +```json +{ + "fieldDefinition" : [ { + "fieldName" : "rec_id", + "matchType" : "dont_use", + "fields" : "rec_id", + "dataType" : "string" + } ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Related types**: + +* `NULL_OR_BLANK` - for fields that should participate in matching but have frequent nulls +* `EXACT` - for fields that should contribute an exact match signal +* `showConcise` flag - hide `DONT_USE` fields from the labeller (CLI command reference) + +**Read more**: [Match types](./) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/email-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/email-match.md new file mode 100644 index 000000000..61b0f4508 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/email-match.md @@ -0,0 +1,120 @@ +--- +description: >- + Compares only the local part of an email address before the @ symbol and + ignores the domain. Built for datasets where the same person appears with + different email domains. +--- + +# EMAIL Match + +### What EMAIL does + +`EMAIL` splits an email address at the `@` symbol and compares only the local part, the portion before `@`. The domain is ignored entirely. + +This handles the common scenario where the same person has a work email and a personal email, or where an organization's email domain changes over time. As long as the local part\ +is the same, EMAIL scores them as a match. + +### How the algorithm works + +_**COMMENT FOR TEAM — Algorithm detail for EMAIL to be added here.**_ + +### What `EMAIL` matches and what it does not + +### When to use `EMAIL` + +
+ +Email address fields in multi-system datasets + +Any dataset where the same person or organisation may appear with different email domains across source systems. Work email in CRM, personal email in e-commerce,\ +university email in a registration system - all with the same local part. + +`EMAIL` ignores the domain and matches on the local part, which is the most stable identifier. + +
+ +
+ +When domain differences are expected and normal + +After an acquisition, employees may appear in one system with their old company domain and in another with their new one. `EMAIL` handles this automatically. + +
+ +### When not to use `EMAIL` + +
+ +When the domain is part of the identity signal + +If you are matching organisations rather than individuals, the domain is often the most reliable identifier. "support@ibm.com" and "support@microsoft.com" have the\ +same local part but are completely different organisations. + +Use `EXACT` on the full email field when the domain matters. + +
+ +
+ +When local parts vary across your data (initials, format differences) + +"j.smith@company.com" and "john.smith@company.com" have different local parts and will not match with `EMAIL`. + +If your data has local part variations like this, consider `FUZZY` on the full email field -accepting that different domains with similar local parts may produce false positives. + +
+ +### Configuring `EMAIL` + +{% tabs %} +{% tab title="Python" %} +### **Community** + +```python +from zingg.client import* + + email = FieldDefinition("email", "string", MatchType.EMAIL) +``` + +### **Enterprise** + +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + + email = EFieldDefinition("email", "string", MatchType.EMAIL) +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). The CLI command is identical for both editions. +{% endhint %} + +```json +{ + "fieldDefinition" : [ { + "fieldName" : "email", + "matchType" : "email", + "fields" : "email", + "dataType" : "string" + } ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Related types**: + +* `EMAIL_OPTIMISED` - same as EMAIL, faster at production scale (ENT) +* `EXACT` - use when the full email including domain must match +* `FUZZY` - use when local parts also have variation + +**Read more**: [Match types](./) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md new file mode 100644 index 000000000..a73c43355 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md @@ -0,0 +1,92 @@ +--- +description: >- + Same matching behavior as EMAIL with faster evaluation on large datasets. The + production-scale choice for email address matching in Enterprise. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# EMAIL\_OPTIMISED Match + +{% hint style="info" icon="right-long" %} +`EMAIL_OPTIMISED` is Enterprise only. The Community (open source) edition does not support this match type. Available in Enterprise Lite and above. +{% endhint %} + +### What it `EMAIL_OPTIMISED` does + +`EMAIL_OPTIMISED` matches email addresses using the same local-part comparison as `EMAIL` it ignores the domain and matches on the portion before `@`. + +The difference is performance: `EMAIL_OPTIMISED` is substantially faster when comparing large numbers of email addresses. + +Use `EMAIL` in development. Switch to `EMAIL_OPTIMISED` for production runs. + +### How the algorithm works + +_**COMMENT FOR TEAM — Same algorithm question as EMAIL applies here.**_ + +### What `EMAIL_OPTIMISED` matches and what it does not + +{% hint style="info" icon="right-long" %} +`EMAIL_OPTIMISED` produces the same matching results as `EMAIL`. + +The behaviour table is identical—only performance differs. +{% endhint %} + +### When to use `EMAIL_OPTIMISED` + +
+ +Email fields on datasets over 1M records + +Any use case where you would use `EMAIL` but your dataset is large enough that\ +performance matters. Customer datasets, patient registries, voter files—any domain where millions of email addresses are being compared. + +
+ +{% hint style="info" icon="right-long" %} +`EMAIL_OPTIMISED` is **Enterprise** only. Use `EMAIL` in Community. +{% endhint %} + +{% tabs %} +{% tab title="Python" %} +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + + email = EFieldDefinition("email", "string", MatchType.EMAIL_OPTIMISED) +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block below uses Enterprise-only match type `EMAIL_OPTIMISED`. The CLI command is identical to other phases. +{% endhint %} + +```json +{ + "fieldDefinition" : [ { + "fieldName" : "email", + "matchType" : "email_optimised", + "fields" : "email", + "dataType" : "string" + } ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Related types**: + +* `EMAIL` - use in Community or in development (all editions) +* `FUZZY_OPTIMISED` - same optimisation pattern for name/text fields + +**Read more**: [Match types](./) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md new file mode 100644 index 000000000..fcee94fbf --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md @@ -0,0 +1,148 @@ +--- +description: >- + No tolerance for variation. Two values must match perfectly for EXACT to score + them as a match. The right choice for trusted identifiers and categorical + fields. +--- + +# EXACT Match + +### What `EXACT` does + +`EXACT` compares two field values and scores them as matching only when they are identical. There is no tolerance for typos, abbreviations, case differences, or format variation. + +Use `EXACT` for any field where a difference in value means a definite difference in entity trusted identifiers like SSN, national ID, passport number, tax code and for categorical fields like country codes and boolean flags where variation is not expected. + +### How the algorithm works + +_**COMMENT FOR TEAM — Algorithm detail for EXACT to be added here.**_ + +### What `EXACT` matches and what it does not + +
Value AValue BMatch?
123-45-6789123-45-6789Yes - identical
123-45-6789123-45-6780No - single-digit difference
USUSYes - identical
USusConfirm with team —
see algorithm note above
truetrueYes
true1Confirm with team —
see algorithm note above
2024-01-152024-01-15Yes
2024-01-1501/15/2024Confirm with team —
different date format
[null]123-45-6789Confirm — null behaviour with EXACT. Add NULL_OR_BLANK to be explicit.
+ +### When to use `EXACT` + +
+ +National identifiers - SSN, passport, tax ID, national ID + +These fields are reliable unique identifiers. An exact match on SSN means the two records are definitively the same entity. Any tolerance on these fields creates false positives that are very difficult to correct after the fact. + +Always use `EXACT` on trusted identifiers. In Zingg Enterprise, also add them as deterministic matching conditions, an exact match on a trusted identifier produces a guaranteed match with score 1 before the probabilistic model runs. + +→ [Deterministic vs Probabilistic Matching](../../entity-resolution/deterministic-vs-probabilistic-matching.md) + +
+ +
+ +Date of birth, event dates + +Dates should not have fuzzy tolerance. "1985-06-15" and "1985-06-16" are different dates of birth - those are different people. + +Use `EXACT` for all date fields. If your date formats vary across source systems, normalise them upstream before matching - do not use `FUZZY` to bridge\ +format differences on dates. + +
+ +
+ +Country codes, boolean flags, categorical variables + +Short categorical codes where variation is not expected: "US", "GB", "IN", true/false,\ +status codes belong with `EXACT`. + +These fields should have no tolerance. If a record says "US" and another says "UK", they are different countries. `FUZZY` would score them as similar (two-character strings\ +sharing one character). `EXACT` does not. + +
+ +### When not to use `EXACT` + +
+ +Any field with known format variation + +Email addresses entered with and without dots, phone numbers with and without country codes, names that appear in short and long form - none of these are safe with `EXACT`. + +`EXACT` on a phone number field will miss "+1-415-555-0123" and "4155550123" as a match even though they are the same number. + +Use `FUZZY` or the field-specific match type (EMAIL for email, PINCODE for postal codes). + +
+ +
+ +Name fields + +Never use `EXACT` on name fields. Even internally consistent data has legitimate name variations - legal name vs common name, maiden name vs married name. Use `FUZZY` for names. + +
+ +### Configuring `EXACT` + +{% tabs %} +{% tab title="Python" %} +### **Community** + +```python +from zingg.client import * + +ssn = FieldDefinition("ssn", "string", MatchType.EXACT) +dob = FieldDefinition("dob", "string", MatchType.EXACT) +``` + +### **Enterprise** + +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + +ssn = EFieldDefinition("ssn", "string", MatchType.EXACT) +dob = EFieldDefinition("dob", "string", MatchType.EXACT) +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). The CLI command is identical for both editions. +{% endhint %} + +```json +{ + "fieldDefinition" : [ + { + "fieldName" : "ssn", + "matchType" : "exact", + "fields" : "ssn", + "dataType" : "string" + }, + { + "fieldName" : "dob", + "matchType" : "exact", + "fields" : "dob", + "dataType" : "string" + } + ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Related types:** + +* `FUZZY` - use when variation is expected +* `PINCODE` - EXACT-like for postal codes with format normalisation built in +* `DONT_USE` - exclude from matching entirely (vs EXACT which contributes\ + a match signal) +* `Deterministic vs Probabilistic Matching` - combine EXACT fields with deterministic rules (Enterprise) + +**Read more**: [Match Types](./) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md new file mode 100644 index 000000000..2682d3bb0 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md @@ -0,0 +1,168 @@ +--- +description: >- + The broadest match type handles typos, abbreviations, transpositions, and + real-world data variation. The default starting point for most name and + address fields. +--- + +# FUZZY Match + +### What `FUZZY` does + +`FUZZY` applies a string similarity comparison to two field values and produces a score reflecting how similar they are. It tolerates the full range of real-world variation: missing characters, transposed letters, common abbreviations, spelling differences, and format inconsistencies. + +It is Zingg's most permissive match type and the right starting point for any field where values can legitimately vary between records representing the same entity. + +### How the algorithm works + +_**COMMENT FOR TEAM—Algorithm detail for FUZZY match type to be added here.**_ + +### What `FUZZY` matches and what it does not + +
Value AValue BMatch?
Jonathan SmithJon SmithYes - abbreviation and short-form variation
John SmithJon SmithYes - single character substitution
J. SmithJohn SmithYes - initial vs full name
Jonathon SmithJonathan SmithYes - common transposition
JohnsonSmithNo - too different, no common characters
[null]John SmithDepends on NULL_OR_BLANK - without it, null matches. With NULL_OR_BLANK added, no match.
+ +_**COMMENT FOR TEAM — Please review and extend this table with examples from real Zingg test runs. Specifically add:**_ + +* _**At least 2 examples from address fields**_ +* _**At least 1 numeric-in-string example (e.g. "42nd Street" vs "42 Street")**_ +* _**At least 1 example showing where FUZZY fails and ONLY\_ALPHABETS\_FUZZY would be better**_ + +### When to use `FUZZY` + +
+ +First name, last name, full name fields + +`FUZZY` is the right match type for name fields in most datasets. Real-world name data contains spelling variations, abbreviations, culturally different name orders, and transliterations that `FUZZY` handles automatically. + +For datasets with known nickname patterns ("Jon" / "Jonathan", "Bill" / "William"),\ +consider combining `FUZZY` with `MAPPING_(nicknames)` to catch cases that are too different for `FUZZY` alone. + +
+ +
+ +Company names and organisation names + +"IBM", "I.B.M.", and "IBM Corporation" have high `FUZZY` similarity. Most common abbreviations and punctuation variants will score above the match threshold. + +For very different representations ("IBM" vs "International Business Machines"), combine `FUZZY` with `MAPPING_(company_names)` to map the canonical form explicitly. + +
+ +
+ +Free-text address lines + +Full address lines benefit from `FUZZY` for the street name component. For structured addresses where the street number and street name are in the same field, consider splitting your approach: + +Use `ONLY_ALPHABETS_FUZZY` for the street name component (ignores numbers, applies fuzzy to letters) and `NUMERIC` for the number component. Or apply `FUZZY` to the full field and accept that number differences will influence the score. + +
+ +### When not to use `FUZZY` + +
+ +Trusted identifiers—SSN, passport, tax ID, national ID + +Never use `FUZZY` on fields that are reliable unique identifiers. `FUZZY` tolerance on an SSN field means "123-45-6789" and "123-45-6780" could score above the match threshold, that is a false positive you cannot afford in a compliance context. + +Use `EXACT` for trusted identifiers. In Enterprise, also consider adding them as deterministic matching conditions, so an exact match on a trusted identifier produces a\ +guaranteed match with score 1, before the probabilistic model runs. + +→ [Deterministic vs Probabilistic Matching](../../entity-resolution/deterministic-vs-probabilistic-matching.md) + +
+ +
+ +Date of birth, registration date, event date + +Dates should always use `EXACT`. A fuzzy comparison between "1985-06-15" and "1985-06-16" might score high enough to produce a match - but those are different people or different events. Use `EXACT` for all date fields. + +
+ +
+ +Postal codes and ZIP codes + +Use `PINCODE` not `FUZZY` for postal codes. `PINCODE` is built to handle the specific format variants postal codes appear in (xxxxx vs xxxxx-xxxx) without introducing the tolerance that `FUZZY` adds, which could match "94102" and "94103" as similar. + +
+ +
+ +When performance at scale is critical + +`FUZZY` is computationally heavier than `FUZZY_OPTIMISED`. For production runs on large datasets where you want the same matching quality with lower CPU and memory cost, use `FUZZY_OPTIMISED` instead. + +→ FUZZY\_OPTIMISED (Enterprise only) + +
+ +### Configuring `FUZZY` + +{% tabs %} +{% tab title="Python" %} +### **Community** + +```python +from zingg.client import* + +fname = FieldDefinition("fname", "string", MatchType.FUZZY) +lname = FieldDefinition("lname", "string", MatchType.FUZZY) +``` + +### **Enterprise** + +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + +fname = EFieldDefinition("fname", "string", MatchType.FUZZY) +lname = EFieldDefinition("lname", "string", MatchType.FUZZY) +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). The CLI command is identical for both editions. +{% endhint %} + +```json +{ + "fieldDefinition" : [ + { + "fieldName" : "fname", + "matchType" : "fuzzy", + "fields" : "fname", + "dataType" : "string" + }, + { + "fieldName" : "lname", + "matchType" : "fuzzy", + "fields" : "lname", + "dataType" : "string" + } + ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Related match types:** + +* `FUZZY_OPTIMISED` - same quality, better performance at scale +* `ONLY_ALPHABETS_FUZZY` - strip numbers first, then apply fuzzy to letters only +* `MAPPING_(FILENAME)` - handle completely different strings (nicknames, abbreviations) that `FUZZY` cannot bridge +* `NULL_OR_BLANK` - combine with FUZZY to treat nulls as non-matches + +**Read more**: [Match Types](./) | [Configure Zingg](../../../running-zingg/configure-zingg.md) | [How Zingg Learns](../) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md new file mode 100644 index 000000000..4172d0fe4 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md @@ -0,0 +1,112 @@ +--- +description: >- + Same matching quality as FUZZY with significantly lower CPU and memory usage. + The production-scale choice when FUZZY accuracy is needed on large datasets. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# FUZZY\_OPTIMISED Match + +{% hint style="info" icon="right-long" %} +`FUZZY_OPTIMISED` is Enterprise only. The Community (open source) edition does not support this match type. Available in Enterprise Lite and above. +{% endhint %} + +### What `FUZZY_OPTIMISED` does + +`FUZZY_OPTIMISED` produces the same matching results as `FUZZY` - it handles typos, abbreviations, transpositions, and real-world variation on name, addresses, and free-text fields.\ +The difference is internal: it uses an optimized implementation that runs with significantly lower CPU and memory usage on large datasets. + +In evaluation and development, use `FUZZY`. In production, especially on datasets over 1 million records where performance and infrastructure cost matter, switch to `FUZZY_OPTIMISED`. + +### How the algorithm works + +_**COMMENT FOR TEAM — Algorithm detail for FUZZY\_OPTIMISED to be added here.**_ + +### What `FUZZY_OPTIMISED` matches and what it does not + +
Value AValue BMatch?
Jonathan SmithJon SmithYes - same as FUZZY
J. SmithJohn SmithYes - same as FUZZY
JonathonJonathanYes - single transposition
JohnsonSmithNo - too different
IBM CorpIBM CorporationYes - abbreviation variant
[null]John SmithDepends - add NULL_OR_BLANK to control null behaviour
+ +_**COMMENT FOR TEAM — Please extend this table with real Zingg test runs comparing `FUZZY` and `FUZZY_OPTIMISED` outputs on the same data to confirm they are identical or document**_\ +\&#xNAN;_**any known differences.**_ + +### When to use `FUZZY_OPTIMISED` + +
+ +Production runs on large datasets + +Any field where you would use `FUZZY` is a candidate for `FUZZY_OPTIMISED` in production. The matching output is the same. The resource cost is lower. + +Switch from `FUZZY` to `FUZZY_OPTIMISED` when you move from evaluation to production, or when a `FUZZY` run is taking longer or consuming more memory than your infrastructure allows. + +
+ +
+ +Name and address fields at scale + +Customer names, company names, street addresses, and city fields on datasets of 1M records or more. The performance improvement is most visible on string fields with high variation, exactly the fields where `FUZZY` is typically applied. + +
+ +### **When not to use `FUZZY_OPTIMISED`** + +
+ +During development and evaluation + +`FUZZY` is simpler to reason about during development. Use `FUZZY` while you are building and tuning your configuration. Switch to `FUZZY_OPTIMISED` before your first production run. + +
+ +{% hint style="info" icon="right-long" %} +`FUZZY_OPTIMISED` is **Enterprise** only. Community users use `FUZZY`. +{% endhint %} + +### Configuring `FUZZY_OPTIMISED` + +{% tabs %} +{% tab title="Python" %} +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + +fname = EFieldDefinition("fname", "string", MatchType.FUZZY_OPTIMISED) +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block below uses Enterprise-only match type `FUZZY_OPTIMISED`. The CLI command is identical to other phases. +{% endhint %} + +```json +{ + "fieldDefinition" : [ { + "fieldName" : "fname", + "matchType" : "fuzzy_optimised", + "fields" : "fname", + "dataType" : "string" + } ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Related Match types:** + +* `FUZZY` - same quality, use in development and evaluation +* `EMAIL_OPTIMISED` - optimised version of EMAIL (Enterprise only) +* `ONLY_ALPHABETS_FUZZY_OPTIMISED` - optimised version for address fields (Enterprise only) + +**Read more:** [Match Types](./) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md new file mode 100644 index 000000000..51bf4d6b4 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md @@ -0,0 +1,127 @@ +--- +description: >- + Resolve known variants of the same value using a user-supplied lookup file - + nicknames, abbreviations, aliases, and company name variants. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# MAPPING\_(FILENAME) Match + +{% hint style="info" icon="right-long" %} +`MAPPING_(FILENAME)` is Enterprise only. The Community edition does not support mapping-based match types. Available in Enterprise Lite and above. +{% endhint %} + +### What `MAPPING_(FileName)` does + +`MAPPING` resolves variants of the same value using a lookup file you provide. Use it when records contain the same entity referred to by different names - nicknames like "Jon" and "Jonathan", company abbreviations like "IBM" and "International Business Machines", or country codes like "US" and "United States" - that you want Zingg to treat as equivalent. + +The mapping file lists groups of equivalent values. Zingg uses it as a lookup during matching: when comparing two records, if both values for a `MAPPING` field appear in the same group, the field is treated as a match. The lookup is bidirectional and case-insensitive - "Jon" matches "Jonathan", "Jonathan" matches "Jon", "JOHN" matches "john". + +`MAPPING` can be combined with another match type using a comma-separated combination in the `matchType` string. Combined with `FUZZY`, Zingg checks the mapping file first and falls back to fuzzy matching for values not in the mapping. + +### How the algorithm works + +_**COMMENT FOR TEAM — Algorithm detail for MAPPING\_(FileName) to be added here.**_ + +### What `MAPPING_(FileName)` matches and what it does not + +
Value AValue BResult with MAPPING onlyResult with MAPPING + FUZZY
JonJonathanMatch - both in same mapping rowMatch
BobRobertMatch - both in same mapping rowMatch
JohnJonathanMatch - both in same mapping rowMatch
JonJhonNo match - "Jhon" not in mappingMatch - caught by FUZZY tolerance
RobertWilliamNo match - different mapping rowsNo match - FUZZY also too dissimilar
Jon[null]No match - null not in mappingNo match - combine with NULL_OR_BLANK
+ +### When to use `MAPPING` + +
+ +Names with nicknames + +For datasets where the same person appears under different familiar names: Jonathan / Jon / Johnny / John; Robert / Bob / Rob / Bobby; William / Will / Bill. `FUZZY` alone catches small variations like "Jon" vs "John" but fails on completely different forms like "Robert" vs "Bob". Use `MAPPING` for the known nickname patterns and combine with `FUZZY` for everything else. + +
+ +
+ +Company names with abbreviations and legal suffixes + +"IBM" / "I.B.M." / "International Business Machines" will never be matched by `FUZZY` alone because the strings are too different. Build a mapping file of canonical company name variants and apply `MAPPING` to the company name field. + +
+ +
+ +Country codes vs full names + +"US" / "USA" / "U.S.A." / "United States" / "United States of America" all represent the same country. A mapping file lists each variant in one group. + +
+ +
+ +Product code variants across systems + +When merging records from systems that use different SKU formats - "SKU-1234" vs "1234" vs "PROD\_1234" - list the canonical variants in a mapping file. + +
+ +### When not to use `MAPPING` + +
+ +Free-text fields with unbounded variation + +If your field has too many variants to list explicitly - full street addresses, free-text descriptions, product specifications - `MAPPING` is the wrong choice. You cannot enumerate the variants. Use `FUZZY` or `TEXT` instead. + +
+ +
+ +Trusted identifiers like SSN, passport, tax ID + +Trusted identifiers should always use `EXACT`, not `MAPPING`. There is no "variant" of an SSN - either the value matches or it does not. Adding mapping logic on a unique identifier introduces room for incorrect matches. + +
+ +
+ +Date and timestamp fields + +Dates should use `EXACT`. Different formats of the same date (`2024-01-15` vs `01/15/2024`) should be normalised upstream of Zingg, not handled through a mapping file. + +
+ +### Configuring `MAPPING` + +The mapping file is a JSON array where each element is an array of equivalent values. + +{% tabs %} +{% tab title="Python" %} +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + fname = EFieldDefinition("fname", "string", "MAPPING_nicknames") +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block below uses Enterprise-only match type `mapping_`. Replace `` with the name of your mapping file without the `.json` extension. The CLI command is identical to other phases. +{% endhint %} + +```json +{ + "fieldDefinition" : [ { + "fieldName" : "fname", + "matchType" : "mapping_nicknames", + "fields" : "fname", + "dataType" : "string" + } ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md new file mode 100644 index 000000000..93bbdc4ca --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md @@ -0,0 +1,117 @@ +--- +description: >- + Changes how Zingg handles null and blank values in a field. Always combined + with another match type, never used alone. +--- + +# NULL\_OR\_BLANK Match + +### What `NULL_OR_BLANK` does + +By default, Zingg treats null values as matching anything. If a field is null in one record and has a value in another, the default behavior scores them as matching on that field. + +`NULL_OR_BLANK` changes this. When added to a field alongside another match type, it tells Zingg to build an explicit feature for null and blank values so the ML model can learn from them rather than defaulting to a match. The model then decides whether nulls should contribute to a match based on your labeled training pairs. + +Use `NULL_OR_BLANK` combined with another match type. It tells Zingg how to treat null and empty values for that field. Used in the `matchType` string with a comma-separated combination. + +### How the algorithm works + +_**COMMENT FOR TEAM -- Algorithm detail for NULL\_OR\_BLANK to be added here.**_ + +### What **`NULL_OR_BLANK`** matches and what it does not + +
Value AValue BMatch?
nullJohn Smith

Without NULL_OR_BLANK: treated as matching (default).

With NULL_OR_BLANK: model learns from labeled examples - likely not a match

[empty string]John SmithSame as above
nullnullConfirm with team - both null behavior
John SmithJohn SmithNo change - NULL_OR_BLANK only affects null/blank values
null[empty stringConfirm with team - null vs empty string
+ +### When to use **`NULL_OR_BLANK`** + +
+ +Fields that are frequently null across source systems + +Middle name, maiden name, suffix, secondary address line, company name on a personal record, any field that is commonly absent in some source systems but present in others. + +Without `NULL_OR_BLANK`, a null middle name in one record, any middle name in another record will match. This is often not what you want - a null middle name should not help two records match. + +Add `NULL_OR_BLANK` alongside `FUZZY` on these fields so the model can learn to treat nulls appropriately from your labeled examples. + +
+ +
+ +When data quality varies significantly across source systems + +If one source system reliably populates a field and another consistently leaves it blank, `NULL_OR_BLANK` prevents the blank-vs-populated pattern from inflating match scores across source system pairs. + +
+ +### When not to use **`NULL_OR_BLANK`** + +
+ +As a standalone match type + +`NULL_OR_BLANK` cannot be used alone. It only modifies the null behaviour of another match type on the same field. + +Always combine: `FUZZY`, `NULL_OR_BLANK` or `EXACT`, `NULL_OR_BLANK` or `PINCODE`, `NULL_OR_BLANK`. + +
+ +
+ +On fields where null means the same thing across all records + +If a field is null in almost all records across all source systems, `NULL_OR_BLANK` adds little value - there are no non-null values for the model to contrast against. Consider `DONT_USE` instead. + +
+ +{% tabs %} +{% tab title="Python" %} +### **Community** + +```python +from zingg.client import * + company = FieldDefinition("company", "string", "FUZZY,NULL_OR_BLANK") +``` + +### **Enterprise** + +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + company = EFieldDefinition("company", "string", "FUZZY,NULL_OR_BLANK") +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). The CLI command is identical for both editions. +{% endhint %} + +```json +{ + "fieldDefinition" : [ { + "fieldName" : "company", + "matchType" : "fuzzy,null_or_blank", + "fields" : "company", + "dataType" : "string" + } ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +`NULL_OR_BLANK` always combines with another match type. Common combinations: + +* `FUZZY`, `NULL_OR_BLANK` - name and address fields often null +* `EXACT`, `NULL_OR_BLANK` - identifier fields that may be absent +* `PINCODE`, `NULL_OR_BLANK` - postal codes missing in some systems +* `DONT_USE` - if the field should be excluded from matching entirely + +**Read more**: [Match Types](./) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md new file mode 100644 index 000000000..50a1709d8 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md @@ -0,0 +1,115 @@ +--- +description: >- + Extracts numbers from strings and compares how many are shared across both + values. Built for street numbers, apartment numbers, and building numbers + embedded in address strings. +--- + +# NUMERIC Match + +### What `NUMERIC` does + +`NUMERIC` extracts all numeric characters from a string field value and compares the numbers found in both records. It scores based on how many extracted numbers are the same across the two values. + +This makes it useful for address fields where a number is embedded in a string - "42 Main St" and "42 Main Street" share the number 42, which `NUMERIC` extracts and matches. The alphabetic parts are not compared by `NUMERIC` - use `ONLY_ALPHABETS_FUZZY` or `FUZZY` for those. + +### How the algorithm works + +_**COMMENT FOR TEAM — Algorithm detail for NUMERIC to be added here.**_ + +### What `NUMERIC` matches and what it does not + +
Value AValue BMatch?
42 Main St42 Main StreetYes - 42 extracted from both
42B Main St42 Main StYes - 42 extracted from both (B ignored)
42 Main St43 Main StNo - different numbers (42 vs 43)
Suite 12, Floor 3Suite 12, Floor 4Partial - 12 matches, 3 vs 4 differs. Score reflects partial overlap.
Flat 4, Tower AFlat 4, Tower AYes - 4 matches
[no numbers]42 Main StConfirm with team —
what score when one side has no numbers?
+ +### When to use `NUMERIC` + +
+ +Street numbers and apartment numbers in address fields + +When your address data has the street number embedded in the same field as the street name - "42 Main Street"; use `NUMERIC` to match the number component. + +For best results on address fields, combine `NUMERIC` with `ONLY_ALPHABETS_FUZZY` on the same field (or split the field into number and name before matching) + +
+ +
+ +Building identifiers, floor numbers, unit numbers + +Any field where a number is the primary discriminating element and it is embedded in a string with non-numeric context. + +
+ +### When not to use `NUMERIC` + +
+ +Fields where the full string matters, not just the numbers + +`NUMERIC` ignores alphabetic characters. "42 Main Street" and "42 Oak Avenue" both contain "42" - `NUMERIC` would score them as matching. The street name difference is invisible to NUMERIC. + +Combine with `ONLY_ALPHABETS_FUZZY` or `FUZZY` so both the number and the text are compared. + +
+ +
+ +Product codes with units + +For "16gb", "500ml", and similar specifications where the unit matters as much as the number, use `NUMERIC_WITH_UNITS` instead. + +
+ +{% tabs %} +{% tab title="Python" %} +### **Community** + +```python +from zingg.client import * + + amount = FieldDefinition("amount", "double", MatchType.NUMERIC) +``` + +### **Enterprise** + +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + + amount = EFieldDefinition("amount", "double", MatchType.NUMERIC) +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). The CLI command is identical for both editions. +{% endhint %} + +```json +{ + "fieldDefinition" : [ { + "fieldName" : "amount", + "matchType" : "numeric", + "fields" : "amount", + "dataType" : "double" + } ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Related matches:** + +* `ONLY_ALPHABETS_FUZZY` - combine with NUMERIC for full address fields +* `NUMERIC_WITH_UNITS` - for product codes that include units (16gb, 500ml) +* `FUZZY` - if you want a single match type covering the whole string + +**Read more**: [Match Types](./) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md new file mode 100644 index 000000000..490aa0bff --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md @@ -0,0 +1,94 @@ +--- +description: >- + Extracts product codes or numbers with units (16gb, 500ml, 2.4GHz) and + compares how many are the same across both values. Built for product + specification fields. +--- + +# NUMERIC\_WITH\_UNITS Match + +### What `NUMERIC_WITH_UNITS` does + +`NUMERIC_WITH_UNITS` extracts number-and-unit pairs from a string field - "16gb," "500ml", "2.4GHz", and compares how many are the same across two records. It is an extension of NUMERIC that recognises units alongside numbers, making it suitable for product catalogs and specification data where a value without its unit is ambiguous. + +### How the algorithm works + +_**COMMENT FOR TEAM — Algorithm detail for NUMERIC\_WITH\_UNITS to be added here.**_ + +### What `NUMERIC_WITH_UNITS` matches and what it does not + +
Value AValue BMatch?
16gb RAM16 GB MemoryYes - 16 GB extracted from both
16gb RAM32gb RAMNo - different numbers
500ml0.5LConfirm with team —
unit normalisation (ml vs L)
2.4GHz Dual Core2.4GHz ProcessorYes - 2.4GHz matches
16gb16Confirm - with vs without unit
+ +### When to use `NUMERIC_WITH_UNIT` + +
+ +Product specification fields + +Any field containing product specifications with units - storage capacity, screen size, processor speed, volume, weight. E-commerce product catalogs, manufacturing BOMs, medical device specifications. + +"16GB RAM, 512GB SSD" and "16 GB RAM, 512 GB SSD" represent the same configuration. `NUMERIC_WITH_UNIT` handles this. + +
+ +### When not to use `NUMERIC_WITH_UNIT` + +
+ +Address fields with numbers + +Use `NUMERIC` for address fields. "42 Main Street" does not have units. `NUMERIC_WITH_UNITS` is built for product specification patterns. + +
+ +{% tabs %} +{% tab title="Python" %} +### **Community** + +```python +from zingg.client import * + + weight = FieldDefinition("weight", "string", MatchType.NUMERIC_WITH_UNITS) +``` + +### **Enterprise** + +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + + weight = EFieldDefinition("weight", "string", MatchType.NUMERIC_WITH_UNITS) +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). The CLI command is identical for both editions. +{% endhint %} + +```json +{ + "fieldDefinition" : [ { + "fieldName" : "weight", + "matchType" : "numeric_with_units", + "fields" : "weight", + "dataType" : "string" + } ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Related types**: + +* `NUMERIC` - for numbers without unit +* `TEXT` - combine for the descriptive parts of product fields + +**Read more**: [Match types](./) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_exact-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_exact-match.md new file mode 100644 index 000000000..8200ce5fa --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_exact-match.md @@ -0,0 +1,96 @@ +--- +description: >- + Strips all numeric characters first, then requires an exact match on the + remaining alphabetic characters. Built for building & location names where + unit numbers vary but name should be identicals. +--- + +# ONLY\_ALPHABETS\_EXACT Match + +### What `ONLY_ALPHABETS_EXACT` does + +`ONLY_ALPHABETS_EXACT` removes all numeric characters from both field values and then compares the remaining alphabetic characters exactly. Two values match if their alphabetic portions are identical after numbers are stripped. + +This is designed for situations where the numeric component of a value varies across records but the alphabetic component should be consistent - building names where flat or unit numbers differ, or wing identifiers where floor numbers change. + +### How the algorithm works + +_**COMMENT FOR TEAM — Algorithm detail for ONLY\_ALPHABETS\_EXACT to be added here.**_ + +### What **`ONLY_ALPHABETS_EXACT`** matches and what it does not + +
Value AValue BMatch?
Tower A, Flat 12Tower A, Flat 7Yes - both strip to "Tower A Flat", exact match
Tower ATower B"No - "Tower A" vs "Tower B" after stripping, different
Block 3, Wing EastBlock 7, Wing EastYes - both strip to "Block Wing East",
exact match
Block 3, Wing EastBlock 3, Wing WestNo - "Wing East" vs "Wing West"
42nd Floor, Suite A18th Floor, Suite AYes - both strip to "nd Floor Suite A" (confirm with team - ordinal suffixes)
+ +### When to use **`ONLY_ALPHABETS_EXACT`** + +
+ +Building names and location identifiers + +Any address field where the building or block name is consistent but unit, flat, or floor numbers vary. The name is the matching signal; the number is noise. + +Combine `ONLY_ALPHABETS_EXACT` with `NUMERIC` on the same field to match both the name (alphabetically) and the number (numerically) independently. + +
+ +### When not to use **`ONLY_ALPHABETS_EXACT`** + +
+ +When the alphabetic portion also has variation + +`ONLY_ALPHABETS_EXACT` requires a perfect alphabetic match after stripping. "Saint James House" and "St James House" will not match - the alphabetic portions are different. + +Use `ONLY_ALPHABETS_FUZZY` instead when the alphabetic component also has spelling or abbreviation variation. + +
+ +{% tabs %} +{% tab title="Python" %} +### **Community** + +```python +from zingg.client import * + street = FieldDefinition("street", "string", MatchType.ONLY_ALPHABETS_EXACT) +``` + +### **Enterprise** + +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + street = EFieldDefinition("street", "string", MatchType.ONLY_ALPHABETS_EXACT) +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). The CLI command is identical for both editions. +{% endhint %} + +```json +{ + "fieldDefinition" : [ { + "fieldName" : "street", + "matchType" : "only_alphabets_exact", + "fields" : "street", + "dataType" : "string" + } ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Related types**: + +* `ONLY_ALPHABETS_FUZZY` - use when alphabetic parts also have variation +* `NUMERIC` - combine to also match the number component + +**Read more**: [Match types](./) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy-match.md new file mode 100644 index 000000000..effd8e58a --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy-match.md @@ -0,0 +1,104 @@ +--- +description: >- + Strips all numeric characters first, then applies fuzzy matching to the + remaining alphabetic characters. Built for address fields where street names + vary & numbers are handled separately. +--- + +# ONLY\_ALPHABETS\_FUZZY Match + +### What ONLY\_ALPHABETS\_FUZZY does + +`ONLY_ALPHABETS_FUZZY` removes all numeric characters from both field values and then applies fuzzy string similarity to the remaining alphabetic characters. Two values match based on how similar their alphabetic portions are with tolerance for typos, abbreviations, and spelling variants. + +This is the standard choice for address street name fields where the street name needs fuzzy comparison and the street number needs to be handled separately. Combine `ONLY_ALPHABETS_FUZZY` with NUMERIC on the same field, or split the field before matching. + +### How the algorithm works + +_**COMMENT FOR TEAM — Algorithm detail for ONLY\_ALPHABETS\_FUZZY to be added here.**_ + +### What **`ONLY_ALPHABETS_FUZZY`** matches and what it does not + +### When to use **`ONLY_ALPHABETS_FUZZY`** + +
+ +Street address lines where number and name are in the same field + +The most common use case: an address line like "42 Main Street" where you want to match "Main Street" fuzzily but not use the number 42 in that comparison. + +Combine with `NUMERIC` on the same field so both components are compared: + +"42 Main St" and "42 Main Street" - `NUMERIC` matches 42, `ONLY_ALPHABETS_FUZZY` matches "Main St" to "Main Street". + +
+ +### When not to use **`ONLY_ALPHABETS_FUZZY`** + +
+ +When the alphabetic portions must match exactly + +Use `ONLY_ALPHABETS_EXACT` when you need the alphabetic component to be identical - for example, building name codes where any alphabetic variation means a different location. + +
+ +
+ +When you want a single match type covering the whole string + +If your address data is structured enough that `FUZZY` on the full string gives good results, use `FUZZY`. `ONLY_ALPHABETS_FUZZY` adds complexity and is most valuable when the number component is causing false positives in a `FUZZY` match. + +
+ +{% tabs %} +{% tab title="Python" %} +### **Community** + +```python +from zingg.client import * + street = FieldDefinition("street", "string", MatchType.ONLY_ALPHABETS_FUZZY) +``` + +### **Enterprise** + +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + street = EFieldDefinition("street", "string", MatchType.ONLY_ALPHABETS_FUZZY) +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). The CLI command is identical for both editions. +{% endhint %} + +```json +{ + "fieldDefinition" : [ { + "fieldName" : "street", + "matchType" : "only_alphabets_fuzzy", + "fields" : "street", + "dataType" : "string" + } ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Related types**: + +* `NUMERIC` - always combine with this for full address fields +* `ONLY_ALPHABETS_EXACT` - use when the name must match exactly (no abbreviation tolerance) +* `ONLY_ALPHABETS_FUZZY_OPTIMISED` - same match type, faster at scale (ENT) +* `FUZZY` - simpler alternative for the full string + +**Read more**: [Match Types](./) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md new file mode 100644 index 000000000..751491a9d --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md @@ -0,0 +1,72 @@ +--- +description: >- + Same matching behavior as ONLY_ALPHABETS_FUZZY with optimised processing for + production-scale address matching. Enterprise only. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# ONLY\_ALPHABETS\_FUZZY\_OPTIMISED Match + +### What it does + +`ONLY_ALPHABETS_FUZZY_OPTIMISED` produces the same matching results as `ONLY_ALPHABETS_FUZZY` - strips numbers, applies fuzzy to the alphabetic portion with optimised processing that reduces CPU and memory usage on large datasets. + +Use `ONLY_ALPHABETS_FUZZY` in development. Switch to `ONLY_ALPHABETS_FUZZY_OPTIMISED` for production runs on large address datasets. + +### How the algorithm works + +_**COMMENT FOR TEAM - Same questions as ONLY\_ALPHABETS\_FUZZY apply. Additionally: what is the performance improvement in practice vs ONLY\_ALPHABETS\_FUZZY on a large address dataset?**_ + +### What **`ONLY_ALPHABETS_FUZZY_OPTIMISED`** matches and what it does not + +{% hint style="info" icon="right-long" %} +`ONLY_ALPHABETS_FUZZY_OPTIMISED` is Enterprise only. The Community (open source) edition does not support this match type. Available in Enterprise Lite and above. +{% endhint %} + +{% tabs %} +{% tab title="Python" %} +{% code expandable="true" %} +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + + street = EFieldDefinition("street", "string", MatchType.ONLY_ALPHABETS_FUZZY_OPTIMISED) +``` +{% endcode %} +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block below uses Enterprise-only match type `ONLY_ALPHABETS_FUZZY_OPTIMISED`. The CLI command is identical to other phases. +{% endhint %} + +```json +{ + "fieldDefinition" : [ { + "fieldName" : "street", + "matchType" : "only_alphabets_fuzzy_optimised", + "fields" : "street", + "dataType" : "string" + } ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Related types**: + +* `ONLY_ALPHABETS_FUZZY` - use in development (all editions) +* `FUZZY_OPTIMISED` - same optimisation pattern for name/text fields +* `NUMERIC` - always combine with this for address fields + +**Read more**: [Match Types](./) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md new file mode 100644 index 000000000..e428e6d1d --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md @@ -0,0 +1,97 @@ +--- +description: >- + Matches postal and ZIP codes across the common format variants they appear + in—5-digit, 9-digit with hyphen, and others. The right choice for postal code + fields instead of EXACT or FUZZY. +--- + +# PINCODE Match + +### What `PINCODE` does + +`PINCODE` is built specifically for postal and PIN code fields. It handles the format variants that postal codes commonly appear in across source systems; a 5-digit US ZIP code and its 9-digit ZIP+4 equivalent. For example, without the tolerance that `FUZZY` would add. + +It is more permissive than `EXACT` (which would not match "94102" and "94102-1234") and more precise than `FUZZY` (which would score "94102" and "94103" as similar). + +### How the algorithm works + +_**COMMENT FOR TEAM — Algorithm detail for PINCODE to be added here.**_ + +### What `PINCODE` matches and what it does not + +
Value AValue BMatch?
9410294102-1234Yes - 5-digit and ZIP+4 same base
9410294103No - different postal codes
EC1A 1BBEC1A1BBConfirm with team —
UK postcode with/without space
110001110001Yes - Indian PIN code, identical
94102941-02Confirm with team —
hyphen in different position
[null]94102Confirm — add NULL_OR_BLANK to control null behaviour
+ +### When to use `PINCODE` + +
+ +Postal code, ZIP code, PIN code fields + +Any field containing a postal code or PIN code. `PINCODE` is always preferable to `EXACT` (which misses format variants) or `FUZZY` (which introduces tolerance that causes false matches between nearby but different codes). + +This is the only match type specifically designed for postal codes + +
+ +### When not to use `PINCODE` + +
+ +Phone numbers or other numeric codes + +`PINCODE` is designed for postal codes specifically. For phone numbers, use `FUZZY` or `NUMERIC`. For product codes with numeric components, use `NUMERIC_WITH_UNITS` or `NUMERIC`. + +
+ +{% tabs %} +{% tab title="Python" %} +### **Community** + +```python +from zingg.client import * + + pincode = FieldDefinition("pincode", "string", MatchType.PINCODE) +``` + +### **Enterprise** + +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + + pincode = EFieldDefinition("pincode", "string", MatchType.PINCODE) +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). The CLI command is identical for both editions. +{% endhint %} + +```json +{ + "fieldDefinition" : [ { + "fieldName" : "pincode", + "matchType" : "pincode", + "fields" : "pincode", + "dataType" : "string" + } ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Related types:** + +* `EXACT` - use when format is guaranteed consistent +* `NUMERIC` - for other numeric identifier fields +* `NULL_OR_BLANK` - combine when postal codes are often missing + +**Read more:** [Match Types](./) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/text-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/text-match.md new file mode 100644 index 000000000..0895d3b34 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/match-types/text-match.md @@ -0,0 +1,115 @@ +--- +description: >- + Compares the overlap of words between two strings. Built for longer + descriptive or free-text fields where word presence matters more than + character-level similarity. +--- + +# TEXT Match + +### What `TEXT` does + +`TEXT` compares two string values by measuring the overlap of words between them, how many words appear in both. It is designed for longer descriptive fields like product descriptions, notes, professional bios, or service descriptions where the overall word content matters more than exact character matches. + +`TEXT` works best when your text fields do not have many typos - the comparison is at word level, not character level, so it will not catch spelling errors within words. For shorter name and address fields, use `FUZZY` instead. + +### How the algorithm works + +_**COMMENT FOR TEAM — Algorithm detail for TEXT to be added here.**_ + +### What `TEXT` matches and what it does not + +
Value AValue BMatch?
Enterprise data management platformData management platform for enterpriseYes - high word overlap
("enterprise", "data", "management",
"platform" all shared)
Enterprise software solutionsConsumer hardware productsNo - low word overlap
Machine learning model trainingTraining machine learning modelsYes - same words, different order
ML modelMachine learning modelPartial - "model" shared,
"ML" vs "Machine learning" differs. Score reflects partial overlap.
[empty]Enterprise data platformConfirm with team —
empty string behaviour
+ +### When to use `TEXT` + +
+ +Product descriptions and catalog fields + +E-commerce product titles and descriptions often contain the same information in different word orders or phrasing. "16GB RAM Laptop with SSD" and "Laptop SSD 16GB RAM" are the same product. `TEXT` captures word-level similarity across these variations. + +Combine with `NUMERIC_WITH_UNITS` for fields containing both descriptive text and numeric specifications. + +
+ +
+ +Professional bios, service descriptions, notes + +Long-form text fields where the same entity appears with different but semantically similar descriptions across systems. `TEXT` handles word-level overlap\ +without requiring exact string similarity. + +
+ +### When not to use `TEXT` + +
+ +Short fields - names, addresses, codes + +`TEXT` is designed for longer strings with multiple words. On a first name field with 1-2 words, `FUZZY` handles character-level variation far better than word overlap. + +Use `FUZZY` for name and address fields. Use `TEXT` for description and notes fields. + +
+ +
+ +Fields with many typos + +`TEXT` compares at word level. "Enterprize" (misspelled) does not overlap with "Enterprise" using `TEXT`. + +If your free-text fields have typos and spelling errors, `FUZZY` handles those better at the character level. + +
+ +{% tabs %} +{% tab title="Python" %} +### **Community** + +```python +from zingg.client import * + description = FieldDefinition("description", "string", MatchType.TEXT) +``` + +### **Enterprise** + +```python +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + description = EFieldDefinition("description", "string", MatchType.TEXT) +``` +{% endtab %} + +{% tab title="JSON" %} +{% hint style="info" icon="right-long" %} +The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). The CLI command is identical for both editions. +{% endhint %} + +```json +{ + "fieldDefinition" : [ { + "fieldName" : "description", + "matchType" : "text", + "fields" : "description", + "dataType" : "string" + } ] +} +``` + +### **CLI** + +```bash +./scripts/zingg.sh --phase findTrainingData --conf config.json +``` +{% endtab %} +{% endtabs %} + +{% hint style="info" icon="right-long" %} +**Related types:** + +* `FUZZY` - better for short fields and when typos are present +* `NUMERIC_WITH_UNITS` - combine for product description fields + +**Read more**: [Match Types](./) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md new file mode 100644 index 000000000..7f0989233 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md @@ -0,0 +1,87 @@ +--- +description: >- + The two models Zingg builds during training - what each one does, why both are + necessary, and how they work together to make entity resolution scalable and + accurate. +--- + +# Zingg Models + +When you run the train phase, Zingg builds two separate machine learning models from your labeled pairs. They solve different parts of the entity resolution problem; one handles scale, the other handles accuracy. + +Understanding what each model does gives you a clear framework for diagnosing problems, tuning performance, and knowing which part of the pipeline to adjust when results are not what\ +you expect. + +### The blocking model - solving the scale problem + +The fundamental challenge of entity resolution at scale is not accuracy. It is the size of the comparison space. + +Comparing every record against every other record produces `N × (N-1) / 2` comparisons, a number that grows quadratically with dataset size. At one million records, that is 500 billion comparisons. At ten million records, it is 50 trillion. No system can evaluate 50 trillion pairs at production cadence. + +The blocking model is Zingg's solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using field heuristics it learns from your training data. Only records within the same bucket are ever compared against each other. Records in different buckets are never compared, which means the blocking model is the first and most\ +consequential filter in the pipeline. + +Typical Zingg blocking reduces the comparison space to 0.05% to 1% of all possible pairs, without losing the pairs that matter. + +The cost of overly aggressive blocking is that it leads to missed matches. If the blocking model places two matching records into different buckets, the similarity model will never compare them. That pair will not appear in your output regardless of how well the similarity model is\ +trained. + +This is why blocking is the first thing to verify when matches you expect are missing. + +{% hint style="success" icon="right-long" %} +The blocking model learns from your labeled training pairs, the same training data that teaches the\ +similarity model. Better training data improves both models. + +For diagnosing blocking coverage and concept details → [Blocking Model](blocking-model.md) +{% endhint %} + +### The similarity model - solving the accuracy problem + +The similarity model evaluates each candidate pair that the blocking model passes through and produces a similarity score: a number between 0 and 1 reflecting how likely it is that the two records represent the same real-world entity. + +It is a classifier, not a rules engine. For each pair, Zingg computes multiple features per field; character-level differences, string lengths, common transpositions, and prefix and suffix overlaps, and combines them into a single prediction. The threshold between match and no-match is automatically optimized. You do not set it manually. + +The similarity model learns from your labeled pairs. Match labels show it what a true match looks like in your specific data. Non-match labels show it, what different entities look like\ +even when their field values are similar. + +This is why label quality matters more than label quantity. A well-chosen set of 30 to 50 match pairs, covering the variation patterns in your schema produces a more accurate model than\ +a large set of casually labeled pairs. + +{% hint style="success" icon="right-long" %} +For diagnosing similarity model behaviour and concept details → [Similarity Model](similarity-model.md) +{% endhint %} + +### The graph algorithm - from pairs to clusters + +After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure. + +If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared. + +This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations. + +In Community, clusters are assigned a `Z Cluster` that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent `Zingg ID` (GUID) that remains stable across runs, incremental updates, and model changes. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* [Identity Graph](../../identity-graph.md) +* [Z Cluster ID vs Zingg ID](../../z-cluster-and-zingg-id.md) +* [Zingg ID](/broken/pages/9QpDFW20AMt0UJ4cEW6b) +* Run [Incremental Matching](../../../running-zingg/run-incremental-matching.md) - how the graph grows over time +{% endhint %} + +### Where models are saved + +
ModelPath
Blocking modelzinggDir/modelId/model/block/
Similarity modelzinggDir/modelId/model/zingg.block
Both modelsWritten during the train phase. Present before any match, link,
or incremental run can proceed.
+ +Use the same `modelId` across all subsequent phases - `match`, `link`, and `runIncremental` to apply both models to your full dataset. + +If you retrain with a new model, use `Compare Model Results (diff phase)` in Enterprise to benchmark the new model against the current one before deploying. Zingg shows you exactly which clusters changed, merged, or split between the two models. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* [Compare Model Results](../../../running-zingg/compare-model-results.md) - benchmark two models before deploying +* [Reassign Zingg ID](../../../running-zingg/reassign-zingg-id.md) - carry existing IDs to a new model +{% endhint %} + diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md new file mode 100644 index 000000000..a99eae095 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md @@ -0,0 +1,86 @@ +--- +description: >- + How to inspect the blocking model, understand its coverage, and fix missed + matches caused by blocking. +--- + +# Blocking Model + +The blocking model is the first filter in every Zingg run. It decides which record pairs the similarity model ever sees. If it places two matching records in different buckets, those records will never be compared, and the match will be missed regardless of how well everything else is configured. + +If your results are missing matches you expect to see, start here. + +### **What `verifyBlocking` output contains** + +The `verifyBlocking` phase produces two output directories under `zinggDir/modelId/blocks/timestamp/`: + +| Output path | What it contains | +| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `counts` | Record count per block. Shows how many records are in each block. Very large blocks indicate poor blocking specificity. | +| `blockSamples` | The top 10% of records associated with each block. Use this to understand which records are being grouped together and whether the groupings make sense. | + +{% hint style="success" icon="right-long" %} +**Read more**: To run the `verifyBlocking` phase and inspect output coverage→ [Verify Blocking](../../../running-zingg/verify-blocking.md) +{% endhint %} + +### Why blocking coverage fails and how to fix it + +Poor blocking coverage means matching pairs are being placed in different buckets and never compared. Four causes account for most cases: + +
+ +Skewed or limited training samples + +The blocking model learns heuristics from your labeled training data. If your training data is skewed; for example, all your labeled pairs are from a single state, city, or category the blocking model learns blocking rules that are too narrow for the full dataset. + +**Fix**: Run `findTrainingData` again and label pairs that represent the full distribution of your data. If your dataset has records from 50 states, your training data should include\ +pairs from multiple states not just the largest. + +
+ +
+ +Manually added training samples overriding learned rules + +If you supplement your training data with manually created pairs rather than pairs selected by `findTrainingData` those pairs, it may teach the blocking model rules that are too specific to those exact examples. The blocking model needs representative pairs selected by Zingg's own candidate selection logic, not hand-curated ones. + +**Fix**: Use `findTrainingData` to generate candidate pairs. Add manually created training data only to supplement coverage gaps, and ensure they are diverse enough that the blocking model can generalize from them. + +
+ +
+ +Too few training examples + +30 to 50 labeled `match` pairs is a good starting point for the similarity model. But the blocking model may need more examples, especially on large datasets with many field variations; before it learns blocking rules that are generic enough to cover the full problem space. + +**Fix**: Run `findTrainingData` and `label` additional round. Pay particular attention to `match` pairs that cover different field value patterns different name formats, address abbreviations, and missing field combinations. + +
+ +
+ +Non-differentiating columns in the schema + +If your field definitions include columns that are the same or nearly the same across a large proportion of your records, for example, a country code that is "US" for 99%\ +of records those columns produce very large blocks. Large blocks slow down matching and indicate that the blocking model is not creating useful partitions. + +**Fix**: Mark non-differentiating columns as `DONT_USE` in your field definitions. `DONT_USE` fields are excluded from both blocking and similarity; they appear in output but do not\ +influence matching. + +
+ +### If `verifyBlocking` shows low coverage, what to do in order + +1. Add more labeled training pairs of the type being missed. The blocking model learns from the same training data as the similarity model. Focus on pairs that represent the variation patterns missing from your current training set. +2. Review your field match types. Fields marked `DONT_US`E are excluded from blocking as well as similarity. If a field has a strong identity signal - a consistent identifier that appears across matching records - consider changing it from `DONT_USE` to `FUZZY`. +3. Check whether missed pairs share a common characteristic. If all missed pairs have empty values in a key field, empty fields cannot contribute to blocking. Either remove that field from your blocking config or improve data completeness upstream. +4. If standard blocking consistently misses a specific pattern in your data, consider custom blocking functions. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* [Label Training Pairs](../../../running-zingg/label-training-pairs.md) - how to add more training data +* [Configure Zingg](../../../running-zingg/configure-zingg.md) - changing field match types +* [Custom Blocking and Similarity](../../../tuning/custom-blocking-and-similarity.md) Functions +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md new file mode 100644 index 000000000..1cefd1a19 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md @@ -0,0 +1,73 @@ +--- +description: >- + How to inspect the similarity model, diagnose false positives and false + negatives, and improve accuracy through targeted retraining. +--- + +# Similarity Model + +The similarity model scores every candidate pair that the blocking model passes through. If your results contain records incorrectly merged into the same cluster (false positives) or matching records that were missed (false negatives), the similarity model is where to investigate after first confirming that blocking is not the cause. + +{% hint style="success" icon="right-long" %} +Before debugging the similarity model, confirm the missed pairs are actually reaching it. If two records are in different blocks, the similarity model never evaluates them regardless of its accuracy. +{% endhint %} + +### Diagnosing false positives records that should not be together + +A false positive is a cluster that contains records representing different real-world entities. They look similar enough that the model merged them, but they should not be in the same cluster. + +
+ +How to identify the cause + +Look at the false positive cluster in your output. Ask: + +Do the records share high values on some fields but clearly differ on fields that should be discriminating? For example, can you have the same first name and city but different dates of birth? + +This scenario is almost always a training data issue. The model has not seen enough non-match-labeled pairs that look similar in some fields but differ in the discriminating ones. It has learned that similarity in those shared fields is enough for a match because you have not shown it the counter-examples. + +**The fix**: Run `findTrainingData` again and find pairs that look like the false positive, similar on the misleading fields, different on the discriminating ones and label them as No Match. The similarity model will learn to use the discriminating fields correctly. + +
+ +
+ +If false positives are widespread, not isolated + +If false positives affect many clusters rather than a specific pattern, check your field match types before adding more training data. + +Fields that should use `EXACT` but are set to `FUZZY` are the most common cause\ +of widespread false positives. Date of birth, SSN, national ID, and tax IDs should always use `EXACT`. Fuzzy tolerance on these fields allows records with different values to score above the match threshold. + +**Also check**: Are there any fields that should be `DONT_USE` contributing to the match?\ +scores? Internal IDs and sequence numbers that happen to appear similar across records can inflate match scores incorrectly. + +
+ +### Diagnosing false negatives matches that were missed + +A false negative is a pair of records that represent the same entity but were not placed in the same cluster. The similarity model scored them below the match threshold or the blocking model never allowed them to be compared. + +#### Confirm it as a similarity problem, not a blocking problem + +Run `verifyBlocking`. If the missed pair is not being blocked together, the similarity model is not the cause. Address the blocking issue first by referring to the [Blocking Model](blocking-model.md) page. + +If `verifyBlocking` confirms the pair is reaching the similarity model but still not matching, proceed to the training data fix below. + +#### Add match labels for the missed pattern + +Run `findTrainingData` and look for pairs similar to the ones being missed. Label them as `match`. The similarity model needs to see this pattern in the training data to learn it. If the variation that\ +causes the mismatch like a specific abbreviation pattern, a missing field, or a transliteration not represented in your labeled pairs; the model has no basis for scoring it above the threshold. + +A small number of targeted labels for the specific pattern being missed is more effective than a large general labeling run. + +{% hint style="success" icon="right-long" %} +Ordering clusters by `avg_min` ascending puts the weakest clusters at the top, those are the ones worth reviewing for false positives first. + +**Read more**: + +* [Interpret Output Scores](../../../interpreting-results/interpret-output-scores.md) - `Z_MINSCORE` and `Z_MAXSCORE` explained +* [Label Training Pairs](../../../running-zingg/label-training-pairs.md) - adding training data for missed patterns +* [Verify blocking](../../../running-zingg/verify-blocking.md) +{% endhint %} + diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/standardize-fields-and-results.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/standardize-fields-and-results.md new file mode 100644 index 000000000..741d91777 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/standardize-fields-and-results.md @@ -0,0 +1,66 @@ +--- +description: >- + Normalize and standardize output fields after entity resolution to produce + consistent golden records. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# Standardize Fields and Results + +{% hint style="info" icon="right-long" %} +Enterprise only. Field standardization is not available in Community. +{% endhint %} + +Field standardization normalizes the values in your match output to a canonical form using a mapping file you provide. For example, 'CEO', 'Chief Executive Officer', and 'Executive Director' all become 'Chief Executive Officer' in the output. Standardization happens after matching and before writing the output. It does not change which records match, only how field values appear in the final result. + +Use it when matched records contain the same entity but use different representations of the same value like job titles, gender codes, country names, and company name variants that need to be normalized before downstream use. + +### **What it does and does not do** + +
BehaviorDetails
When it runsAfter matching, before writing the output.
It does not affect matching behaviour it transforms output values only
What it applies toAny field you configure it on. One field can have one mapping file.
What it does not apply toPrimary key fields. These are never postprocessed.
Case sensitivityLookup is case-insensitive. CEO, ceo, and Ceo all match
the same mapping entry.
What it does not handleExtra whitespace, punctuation differences, or partial matches. Those need to be cleaned upstream.
+ +### Where this is useful + +
+ +Human resources and org data + +Standardise job titles and roles across HR, payroll, and directory systems to produce consistent headcount reporting and org charts. "VP Engineering", "Vice President of Engineering", and "VP Eng" all map to the same canonical title. + +
+ +
+ +Sales and CRM + +Normalise company names for consistent account consolidation and revenue analytics.\ +"IBM", "I.B.M.", and "International Business Machines" resolve to a single canonical account name in your output. + +
+ +
+ +Product and catalog management + +Unify legacy product codes and new SKU formats across inventory, billing, and e-commerce systems for clean catalog reconciliation. + +
+ +
+ +Compliance and regulatory reporting + +Enforce canonical values on classification fields like industry codes, status labels, category names to simplify downstream regulatory reporting and reduce manual normalization before submissions. + +
+ +{% hint style="success" icon="right-long" %} +**Read more**: + +* To create a mapping file and configure the postprocessor on a field → [Configure Field Standardization](../../../tuning/configure-field-standardization.md) +* For the full field definition configuration including all `EFieldDefinition` methods - [Configure Zingg](../../../running-zingg/configure-zingg.md) +* For dictionary-based matching using the MAPPING match type (nicknames, aliases, company names) - [Match Types](../match-types/) +{% endhint %} diff --git a/docs/zingg-concepts/identity-graph.md b/docs/zingg-concepts/identity-graph.md new file mode 100644 index 000000000..b94219944 --- /dev/null +++ b/docs/zingg-concepts/identity-graph.md @@ -0,0 +1,53 @@ +--- +description: >- + Zingg's underlying data structure for resolved entities and how it differs + between Community and Enterprise. +tags: + - ent +--- + +# Identity Graph + +Zingg produces an identity graph as its output. Nodes are records. Edges connect records that Zingg has identified as representing the same real-world entity. A cluster is a connected component in this graph, a group of records that all resolve to the same entity. Not all entity resolution systems work this way; some only produce pairwise match decisions without joining them into a graph. Zingg always produces the complete graph + +Zingg's identity graph is this structure. What differs between Community and Enterprise is not the graph itself but how it is managed over time. + +{% hint style="success" icon="right-long" %} +* **Open Source:** `Z Cluster`, non-persistent and may change between runs. +* **Enterprise:** `Zingg ID`, stable, globally unique, persistent across incremental updates. +{% endhint %} + +
+ +### Community - a complete identity graph, rebuilt fresh every run + +Every Zingg Community run produces a complete, queryable identity graph. Every record in your dataset is assigned a `Z Cluster`. All records sharing the same `Z Cluster` represent the same real-world entity. + +You can query it, export it, join it with other datasets, power analytics, and feed it into downstream models immediately after every run. You do not need to install anything beyond your existing Spark environment, write rules, or maintain a separate MDM system to get a working identity graph from your data. + +The `Z Cluster` is non-persistent between runs, and it is reassigned fresh each time the match job runs. This means the graph is always accurate to your latest data, but the IDs themselves are not safe to store as stable references in downstream systems. For applications where the identity identifier needs to persist across re-runs, incremental updates, and model changes, that is the boundary where Enterprise begins. + +### Enterprise - persistent graph + +In Enterprise, the identity graph is persistent. Once a `Zingg ID` is assigned to a resolved entity, it does not change between incremental runs. New and updated records are incorporated into the existing graph. Clusters merge, split, and grow, but `Zingg IDs` for established clusters remain stable. Downstream systems can store and reference `Zingg IDs` with confidence that they will remain valid. + +The incremental flow is, at its core, an engine for maintaining and updating this persistent identity graph as your data changes. Cluster merges, unmerges, and new record assignments all happen automatically. New runs do not override human-approved cluster decisions. + +{% hint style="warning" icon="right-long" %} +Persistent identity graph with stable Zingg ID across runs is Enterprise only.\ +[Talk to us about Enterprise](https://www.zingg.ai/company/contact/contact). +{% endhint %} + +{% hint style="success" icon="right-long" %} +Community gives you a complete, accurate identity graph after every run. + +Enterprise gives you the same graph with stable `Zingg IDs` , so the graph can grow incrementally and downstream systems can reference it safely across time. + +**Read more**: + +* [Z Cluster vs Zingg ID](z-cluster-and-zingg-id.md) +* [Zingg Models](how-zingg-learns/zingg-models/) +* [Run incremental matching](../running-zingg/run-incremental-matching.md) +* [Community Vs Enterprise](community-vs-enterprise/) +{% endhint %} + diff --git a/docs/zingg-concepts/pass-through.md b/docs/zingg-concepts/pass-through.md new file mode 100644 index 000000000..23c7f8f40 --- /dev/null +++ b/docs/zingg-concepts/pass-through.md @@ -0,0 +1,104 @@ +--- +description: >- + Exclude specific records from matching while still including them in the Zingg + output with their own Zingg ID. +tags: + - ent + - tag: enterprise-only + primary: true +--- + +# Pass Through + +{% hint style="info" icon="right-long" %} +Enterprise only. Pass Through excludes specific records from the matching process while still including them in the output. Each Pass Through record receives its own unique Zingg ID. +{% endhint %} + +Not all records in your dataset should participate in matching. Some records are partially populated, corrupted, bot-generated, or have attributes that should not influence cluster formation, but they still need to appear in the output for governance, compliance, or audit purposes. + +`Pass Through` lets you define a filter expression for these records. Records that meet the pass through condition are excluded from matching, linking, and incremental processes. They are\ +carried through to the output unchanged, and each receives its own unique `Zingg ID`. + +### When to use Pass Through + +
+ +Use case 1: Bot-generated records + +Some datasets contain records created by bots or automated processes rather than real entities. These records are identifiable by a synthetic email pattern, a placeholder name, or a specific marker field. + +Including these items in the matching process inflates the sizes of the clusters and degrades the accuracy of the results. `Pass Through` lets you exclude them by defining a filter expression on the identifying field. + +Example: Exclude all records where the email field contains "`@bot`." or where a marker field equals "`AUTOMATED`". + +These records still appear in the output with their own `Zingg ID`s so they can be traced and audited. + +
+ +
+ +Use case 2: Corrupted or spam records + +Some records are corrupted, incomplete, or contain spam data that would produce false matches if included in the matching process. Common examples: records with all fields blank except an ID, records with placeholder values like "`TEST`" or "`NULL`", or records flagged by an upstream data quality check. + +`Pass Through` lets you filter these records out of matching while keeping them in the output for auditing purposes. The audit trail shows which records were treated as `Pass Through` and why. + +
+ +
+ +Use case 3: Attribute exclusion + +Occasionally a specific attribute value should prevent a record from influencing cluster formation, even if the record itself is valid. For example, a gender field that should not factor into matching, or a record status flag that indicates the record is archived or inactive. + +`Pass Through` lets you filter by any field expression. Records matching the condition are excluded from matching entirely, even if their other fields would produce a valid match. + +
+ +### How to configure Pass Through + +`Pass Through` is not a separate phase; it is a filter expression you set on your `args` object in [Configure Zingg](../running-zingg/configure-zingg.md), before running any phase. Records matching the expression are excluded from matching, linking, and incremental runs automatically. + +{% tabs %} +{% tab title="Enterprise" %} +#### Python + +Set the pass through expression on your args. + +```python +args.setPassthroughExpr("fname = 'matilda'") +``` + +#### JSON + +```json +{ "passthroughExpr" : "fname = 'matilda'" } +``` + +For nullable fields, ensure the negative of your expression correctly identifies non-passthrough records: + +```json +{ "passthroughExpr" : "is_deceased = true AND is_deceased IS NOT NULL" } +``` + +Zingg internally applies the negation of `passthroughExpr` to filter which records participate in matching. If you apply the condition to a nullable field without the null guard, records with null values in that field may behave unexpectedly. +{% endtab %} + +{% tab title="Enterprise Snowflake" %} +_**CHECK WITH SONAL - Enterprise Snowflake content for this topic to be provivded by Sonal**_ +{% endtab %} +{% endtabs %} + +### Pass Through records in the output + +`Pass Through` records appear in the match output alongside normally matched records. The key difference: + +* Normally matched records share a `Zingg ID` with other records in their cluster. +* `Pass Through` records each receiving their own unique `Zingg ID`. They are not grouped with any other record. They are traceable and auditable but do not contribute to or inherit any entity cluster. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* Pass Through concept in the glossary - [Concepts glossary](concept-glossary.md) +* Configure Zingg where Pass Through is set up - [Configure Zingg](../running-zingg/configure-zingg.md) +{% endhint %} diff --git a/docs/zingg-concepts/z-cluster-and-zingg-id.md b/docs/zingg-concepts/z-cluster-and-zingg-id.md new file mode 100644 index 000000000..e153bc3be --- /dev/null +++ b/docs/zingg-concepts/z-cluster-and-zingg-id.md @@ -0,0 +1,69 @@ +--- +description: >- + The key difference between Community and Enterprise output, and why it matters + for every system that consumes Zingg's results. +--- + +# Z Cluster and Zingg ID + +Zingg writes a cluster identifier into your match output so every record belonging to the same resolved entity carries the same value. Community uses `Z_CLUSTER`, a non-persistent identifier. Enterprise uses `Zingg_ID`, a persistent globally unique identifier. This page covers the difference between the two, the lifecycle behaviour, and how to use Zingg ID in your downstream systems. + +### `Z_Cluster` - Community + +When Zingg Community resolves a set of records into a cluster, it assigns a `Z Cluster` to every record in that cluster. All records in the same cluster share the same `Z Cluster` value in the output. This is how you identify which records resolved to the same entity. + +`Z Cluster` is non-persistent. Each time you run the match job, Zingg rebuilds the identity graph from scratch and assigns new cluster identifiers. The same cluster of records may receive a completely different value on the next run. + +This is expected behavior for Community, but it means you cannot store `Z Cluster` in downstream systems and expect it to remain valid across runs. + +
+ +What breaks downstream if Z_Cluster changes between runs? + +Any system that stores `Z_Cluster` as a customer or entity identifier will break when the match job re-runs. + +Common failure scenarios: + +* A CRM storing `Z_Cluster` as the unified customer key - after re-run, the key no longer exists or resolves to a different cluster +* An analytics dashboard segmenting by cluster - segment membership changes unpredictably +* An AI model trained on `Z_Cluster`-based features - features become invalid after each re-run +* A compliance system linking transactions to a cluster identifier - audit trail breaks + +If any downstream system needs a stable entity identifier to reference across time, `Z_Cluster` is not suitable. This is the core use case for Zingg Enterprise and the `Zingg_ID`. + +
+ +{% hint style="warning" icon="right-long" %} +Need stable entity IDs across runs? [Talk to us about Enterprise](https://www.zingg.ai/company/contact/contact). +{% endhint %} + +### `Zingg_ID` - Enterprise + +Zingg Enterprise assigns a globally unique, persistent GUID - the `Zingg ID` , to each resolved entity. Once assigned, the `Zingg ID` is stable across incremental runs. New records that match an existing cluster inherit its `Zingg ID`, and cluster merges are handled automatically. + +However, if cluster membership changes significantly between runs. For example, a cluster splits due to new data or updated training, the `Zingg ID` may be reassigned. Downstream systems should treat the `Zingg ID` as stable within a deployment lifecycle, not as an indefinitely immutable identifier. + +#### **Lifecycle behaviour** + +
ScenarioZingg_ID behaviour
New record matches an existing clusterInherits the cluster's existing Zingg_ID
New record does not match any existing clusterReceives a new Zingg_ID
Cluster merges with another in an incremental runMerge is handled automatically
Cluster splits due to new data or updated trainingZingg_ID may be reassigned
Human-approved cluster decisions from previous runsPreserved, not overridden
Model is retrainedUse Reassign Zingg_ID to carry existing IDs to the new model
+ +### The `Zingg_ID` column in your output + +In your match or incremental output, the Zingg ID appears as a column named `Zingg_ID` alongside your input fields. Every record in the same resolved cluster shares the same `Zingg_ID` value. + +Use this column to: + +* Join Zingg output back to your source tables +* Build a golden record by grouping on `Zingg_ID` and selecting the best field values across each group +* Track the same entity across incremental runs +* Pass a stable entity reference to downstream CRM, analytics, or compliance systems + +{% hint style="success" icon="right-long" %} +**Read more:** + +* [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) - for the full reference of every Zingg output column +* [Identity Graph](identity-graph.md) - how Zingg's resolved entities form a graph structure +* [Run Incremental Matching](../running-zingg/run-incremental-matching.md) - how Zingg IDs persist when new records arrive +* [Reassign Zingg ID](../running-zingg/reassign-zingg-id.md) - preserve Zingg IDs across model retraining or platform migration +* [Concept Glossary](concept-glossary.md) - every Zingg term in one place +{% endhint %} diff --git a/docs/zingg-python-api/community-python-api.md b/docs/zingg-python-api/community-python-api.md new file mode 100644 index 000000000..bc9875de7 --- /dev/null +++ b/docs/zingg-python-api/community-python-api.md @@ -0,0 +1,274 @@ +--- +description: >- + The open-source Python interface to Zingg. Configure arguments, define fields, + build pipes, and run every Community phase from Python. +--- + +# Community Python API + +The Community Zingg Python API is the open source way to run Zingg from Python. It provides classes for arguments, field definitions, client options, pipes, and the Zingg execution client. + +Use this package when you want a free, open-source Zingg installation running on Spark. For Enterprise features like deterministic matching, blocking strategy, primary keys, pass-through, mapping match types, and incremental matching, see [Enterprise ZinggEC Python API](enterprise-zinggec-python-api.md). + +### Requirements + +* Python 3.6+ +* Spark 3.5.0 + +### Install the package + +```bash +pip install zingg +``` + +### Modules and classes + +#### **`zingg.client` module** + +
ClassPurpose
ZinggThe Zingg execution client. Pass it args and options, call initAndExecute().
ZinggWithSparkSame as Zingg but for use when an existing Spark session is already present (e.g. in a Databricks or Fabric notebook).
ArgumentsConfiguration object holding the model ID, Zingg directory, partition count, field definitions, and pipes.
ClientOptionsHolds the phase name. Pass to the Zingg execution client.
FieldDefinitionDefines a single field with its name, data type, and match type.
+ +#### **`zingg.pipes` module** + +
ClassPurpose
PipeBase class for any input or output. Use for generic JDBC, custom formats, or any source where the specialised classes do not fit.
CsvPipeRead or write CSV files.
BigQueryPipeRead from or write to Google BigQuery.
SnowflakePipeRead from or write to Snowflake (via Spark connector).
+ +{% hint style="success" icon="right-long" %} +Full auto-generated method signatures for every class above are at the [Zingg Python OSS API reference on GitHub](https://github.com/zinggAI/zingg/blob/main/docs/pythonOss/markdown/zingg.client.md). +{% endhint %} + +### Imports + +```python +from zingg.client import * +from zingg.pipes import * +``` + +### Build the arguments object + +```python +args = Arguments() +args.setModelId("100") +args.setZinggDir("models") +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.5) +``` + +### Define fields and match types + +```python +fname = FieldDefinition("fname", "string", MatchType.FUZZY) +lname = FieldDefinition("lname", "string", MatchType.FUZZY) +stNo = FieldDefinition("stNo", "string", MatchType.FUZZY) +add1 = FieldDefinition("add1", "string", MatchType.FUZZY) +add2 = FieldDefinition("add2", "string", MatchType.FUZZY) +city = FieldDefinition("city", "string", MatchType.FUZZY) +areacode = FieldDefinition("areacode", "string", MatchType.FUZZY) +state = FieldDefinition("state", "string", MatchType.FUZZY) +dob = FieldDefinition("dob", "string", MatchType.FUZZY) +ssn = FieldDefinition("ssn", "string", MatchType.FUZZY) + +fieldDefs = [ + fname, lname, stNo, add1, add2, city, + areacode, state, dob, + ssn +] +args.setFieldDefinition(fieldDefs) +``` + +{% hint style="success" icon="right-long" %} +**Read more**: For all match types and combinations → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +{% endhint %} + +### Configure input and output pipes + +#### CSV pipe example: + +```python +schema = ( + "id string, fname string, " + "lname string, stNo string, " + "add1 string, add2 string, " + "city string, areacode string, " + "state string, dob string, " + "ssn string" +) + +inputPipe = CsvPipe("testFebrl", "examples/febrl/test.csv", schema) +args.setData(inputPipe) + +outputPipe = CsvPipe("resultFebrl", "/tmp/febrlOutput") +args.setOutput(outputPipe) +``` + +#### For BigQuery + +```python +bqPipe = BigQueryPipe("bqInput", "your-project.your-dataset.your-table") +``` + +#### For Snowflake + +```python +snowPipe = SnowflakePipe("snowInput", "your-snowflake-table") +``` + +#### For generic pipes + +```python +genericPipe = Pipe("genericInput", "jdbc") +genericPipe.addProperty("url", "jdbc:postgresql://host:5432/db") +genericPipe.addProperty("dbtable", "customers") +genericPipe.addProperty("driver", "org.postgresql.Driver") +genericPipe.addProperty("user", "your_user") +genericPipe.addProperty("password", "your_password") +``` + +{% hint style="success" icon="right-long" %} +**Read more**: For all connector formats and full pipe configuration → [Connect Your Data](https://app.gitbook.com/s/4FvYw4VaCJcugJzWCiLX/connect-your-data) +{% endhint %} + +### Execute Zingg phases + +Run any phase by passing its name to `ClientOptions`. The same pattern works for `findTrainingData`, `label`, `train`, `match`, `link`, `findAndLabel`, `updateLabel`, `generateDocs`, and any other Community phase. + +#### **Run `findTrainingData`:** + +```python +options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `label`:** + +```python +options = ClientOptions([ ClientOptions.PHASE, "label" ]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `findAndLabel`** + +Combines `findTrainingData` and `label` into one call — use for smaller datasets where `findTrainingData` runs quickly. + +```python +options = ClientOptions([ ClientOptions.PHASE, "findAndLabel" ]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `updateLabel`** + +Revisit and correct previously marked pairs — run `generateDocs` first to identify pairs to update + +```python +options = ClientOptions([ ClientOptions.PHASE, "updateLabel" ]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `generateDocs`** + +```python +options = ClientOptions([ ClientOptions.PHASE, "generateDocs" ]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `train`** + +```python +options = ClientOptions([ ClientOptions.PHASE, "train" ]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `match`** + +```python +options = ClientOptions([ ClientOptions.PHASE, "match" ]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `link`** + +```python +options = ClientOptions([ ClientOptions.PHASE, "link" ]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +### Using `ZinggWithSpark` in notebooks + +When running inside a Databricks, Fabric, or other notebook where a Spark session already exists, use `ZinggWithSpark` instead of `Zingg`: + +```python +zingg = ZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +`ZinggWithSpark` reuses the existing Spark session instead of creating a new one. This is the recommended class for notebook environments. + +### Full example + +Complete working example combining all the steps above: + +```python +from zingg.client import * +from zingg.pipes import * + +args = Arguments() + +fname = FieldDefinition("fname", "string", MatchType.FUZZY) +lname = FieldDefinition("lname", "string", MatchType.FUZZY) +stNo = FieldDefinition("stNo", "string", MatchType.FUZZY) +add1 = FieldDefinition("add1", "string", MatchType.FUZZY) +add2 = FieldDefinition("add2", "string", MatchType.FUZZY) +city = FieldDefinition("city", "string", MatchType.FUZZY) +areacode = FieldDefinition("areacode", "string", MatchType.FUZZY) +state = FieldDefinition("state", "string", MatchType.FUZZY) +dob = FieldDefinition("dob", "string", MatchType.FUZZY) +ssn = FieldDefinition("ssn", "string", MatchType.FUZZY) + +fieldDefs = [ + fname, lname, stNo, add1, add2, city, + areacode, state, dob, + ssn +] +args.setFieldDefinition(fieldDefs) + +args.setModelId("100") +args.setZinggDir("models") +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.5) + +schema = ( + "id string, fname string, " + "lname string, stNo string, " + "add1 string, add2 string, " + "city string, areacode string, " + "state string, dob string, " + "ssn string" +) + +inputPipe = CsvPipe("testFebrl", "examples/febrl/test.csv", schema) +args.setData(inputPipe) + +outputPipe = CsvPipe("resultFebrl", "/tmp/febrlOutput") +args.setOutput(outputPipe) + +options = ClientOptions([ + ClientOptions.PHASE, + "match" +]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +### Example notebooks and reference + +Working example notebooks are at `github.com/zinggAI/zingg/tree/main/examples/febrl`. Start with `FebrlExample.py`. + +{% hint style="success" icon="right-long" %} +**Read more**: Auto-generated API reference for every class and method: [Zingg Community Python API on GitHub](https://github.com/zinggAI/zingg/blob/main/docs/pythonOss/markdown/zingg.client.md) +{% endhint %} diff --git a/docs/zingg-python-api/enterprise-zinggec-python-api.md b/docs/zingg-python-api/enterprise-zinggec-python-api.md new file mode 100644 index 000000000..d4643623d --- /dev/null +++ b/docs/zingg-python-api/enterprise-zinggec-python-api.md @@ -0,0 +1,489 @@ +--- +description: >- + Adds deterministic matching, blocking strategy, primary keys, pass-through, + mapping match types, incremental matching, cluster approval, output stats, and + additional pipe types. +tags: + - tag: enterprise-only + primary: true +--- + +# Enterprise ZinggEC Python API + +ZinggEC is the Zingg Enterprise Common Python API. It extends the Community API with features required for production identity resolution - persistent Zingg IDs, deterministic matching, pass-through, output statistics, incremental matching, cluster approval, and additional pipe types for Unity Catalog and in-memory data. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* [Community Python API](community-python-api.md) - Community (open source) +* [Enterprise ZinggES Python API](enterprise-zingges-python-api.md) - Enterprise Plus with Spark client +{% endhint %} + +{% hint style="warning" icon="right-long" %} +ZinggEC requires a Zingg Enterprise licence and the `zinggEC` package. [Contact Zingg to get access](https://www.zingg.ai/company/contact/contact). +{% endhint %} + +### Requirements + +* Python 3.6+ +* Spark 3.5.0 +* Zingg Enterprise licence + +### Install the package + +```bash +pip install zinggEC +``` + +### Modules and classes + +#### **`zinggEC.enterprise.common` package** + +
ModuleClassesPurpose
ApproverArgumentsApproverArgumentsCluster approval workflow - set approval query and destination for human-reviewed clusters
IncrementalArgumentsIncrementalArgumentsIncremental matching - incremental data, deleted data, delete action, output temp directory, parent args
MappingMatchTypeMappingMatchTypeUser-supplied lookup file for nickname/abbreviation matching
epipesECsvPipe, EPipe, InMemoryPipe, UCPipeEnterprise pipe types including CSV, base pipe, in-memory DataFrame, and Unity Catalog table
EArgumentsEArguments, DeterministicMatchingEnterprise arguments object plus deterministic matching rules
EFieldDefinitionEFieldDefinitionEnterprise field definition with primary key support and mapping match type
+ +{% hint style="success" icon="right-long" %} +Full auto-generated method signatures for every class are at the [Zingg Enterprise Common Python API reference on GitHub](https://github.com/zinggAI/zingg/blob/main/docs/pythonEC/markdown/zinggEC.md). +{% endhint %} + +### Imports + +```python +from zingg.client import * +from zingg.pipes import * +from zinggEC.enterprise.common.ApproverArguments import * +from zinggEC.enterprise.common.IncrementalArguments import * +from zinggEC.enterprise.common.MappingMatchType import * +from zinggEC.enterprise.common.epipes import * +from zinggEC.enterprise.common.EArguments import * +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition +``` + +### Build the Enterprise arguments object + +```python +args = EArguments() +args.setModelId("100") +args.setZinggDir("./models") +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.5) +``` + +Set the blocking strategy. If not set, the model uses `DEFAULT`. `WIDER` is also available for cases where you want to cast a wider blocking net. + +```python +args.setBlockingModel("DEFAULT") +``` + +### Define fields with `EFieldDefinition` + +```python +recId = EFieldDefinition("recId", "string", MatchType.DONT_USE) +recId.setPrimaryKey(True) + +fname = EFieldDefinition("fname", "string", MatchType.FUZZY) +lname = EFieldDefinition("lname", "string", MatchType.FUZZY) +stNo = EFieldDefinition("stNo", "string", MatchType.FUZZY) +add1 = EFieldDefinition("add1", "string", MatchType.FUZZY) +add2 = EFieldDefinition("add2", "string", MatchType.FUZZY) +city = EFieldDefinition("city", "string", MatchType.FUZZY) +areacode = EFieldDefinition("areacode", "string", MatchType.FUZZY) +state = EFieldDefinition("state", "string", MatchType.FUZZY) +dob = EFieldDefinition("dob", "string", MatchType.FUZZY) +ssn = EFieldDefinition("ssn", "string", MatchType.FUZZY) + +fieldDefs = [ + recId, fname, lname, stNo, add1, add2, + city, areacode, state, dob, + ssn +] +args.setFieldDefinition(fieldDefs) +``` + +{% hint style="info" icon="right-long" %} +`setPrimaryKey(True)` marks the primary key field, required for `runIncremental` to track records correctly across runs. +{% endhint %} + +### **Using `MappingMatchType` on a field** + +For fields where you want to use a mapping file (nicknames, abbreviations, aliases), pass a `MappingMatchType` to `EFieldDefinition`: + +```python +fname = EFieldDefinition("fname", "string", MatchType.FUZZY, + MappingMatchType("MAPPING", "NICKNAMES_TEST")) +``` + +The second argument to `MappingMatchType` is the mapping file name (without extension). The mapping file must be present in your working directory. + +{% hint style="success" icon="right-long" %} +**Read more**: For the full mapping file format and rules → [Mapping match type](../zingg-concepts/how-zingg-learns/match-types/) +{% endhint %} + +### Pass through + +Exclude records matching the expression from matching while still keeping them in the output: + +```python +args.setPassthroughExpr("fname = 'matilda'") +``` + +For null-safe expressions: + +```python +args.setPassthroughExpr( + "is_deceased = true " + "AND is_deceased IS NOT NULL") +``` + +{% hint style="success" icon="right-long" %} +Pass through excludes records from cluster formation. They still appear in the identity graph and output with their own Zingg ID. Zingg internally applies the negation of `passthroughExpr` to filter matching records - ensure the negative of your expression yields the records that are NOT pass-through. +{% endhint %} + +### Deterministic matching + +Add hard rules where exact field matches always result in a match, regardless of probabilistic score: + +```python +dm1 = DeterministicMatching('fname', 'stNo', 'add1') +dm2 = DeterministicMatching('ssn') +dm3 = DeterministicMatching('fname', 'stNo', 'lname') +args.setDeterministicMatchingCondition(dm1, dm2, dm3) +``` + +{% hint style="success" icon="right-long" %} +**Read more**: For full deterministic matching concepts and configuration → [Deterministic vs Probabilistic Matching](../zingg-concepts/entity-resolution/deterministic-vs-probabilistic-matching.md) +{% endhint %} + +### Output statistics + +Enterprise writes match quality statistics to three separate files using the `$ZINGG_DYNAMIC_STAT_NAME` placeholder, replaced at runtime with `SUMMARY`, `CLUSTER`, and `RECORD`: + +```python +statsOutputPipe = ECsvPipe("stats", "/tmp/zinggStats_$ZINGG_DYNAMIC_STAT_NAME") +statsOutputPipe.setHeader("true") +args.setOutputStats(statsOutputPipe) +``` + +{% hint style="success" icon="right-long" %} +**Read more**: For statistics interpretation → [Output Statistics](../interpreting-results/output-statistics.md) +{% endhint %} + +### Pipes - Enterprise edition + +#### **`ECsvPipe`** + +CSV input/output. Same as `CsvPipe` but for Enterprise. + +```python +inputPipe = ECsvPipe("testFebrl", "examples/febrl/test.csv", schema) +args.setData(inputPipe) + +outputPipe = ECsvPipe("resultFebrl", "/tmp/febrlOutput") +outputPipe.setHeader("true") +args.setOutput(outputPipe) +``` + +#### **`EPipe`** + +Base pipe class with pass-through capability. Use for any source where the specialised classes do not fit. + +#### **`InMemoryPipe`** + +For reading from or writing to an in-memory Spark DataFrame instead of a file: + +```python +inMemPipe = InMemoryPipe("testFebrl") +inMemPipe.setDataset(df) +args.setData(inMemPipe) +``` + +Use this in notebook environments where you already have data in a DataFrame and do not want to write it to disk first. + +#### **`UCPipe`** + +For Databricks Unity Catalog tables: + +```python +ucPipe = UCPipe("testFebrl") +ucPipe.setTable("catalog.schema.your_table") +args.setData(ucPipe) +``` + +Use this when your data lives in a Unity Catalog table on Databricks. + +### Execute Zingg Enterprise phases + +ZinggEC uses the `EZingg` client. Pattern is the same as Community — set options with the phase name, call `initAndExecute()`. + +#### **Run `findTrainingData`** + +```python +options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `label`** + +```python +options = ClientOptions([ ClientOptions.PHASE, "label" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `findAndLabel`** + +Enterprise convenience — combines `findTrainingData` and `label` + +```python +options = ClientOptions([ ClientOptions.PHASE, "findAndLabel" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `generateDocs`** + +```python +options = ClientOptions([ ClientOptions.PHASE, "generateDocs" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `train`** + +```python +options = ClientOptions([ ClientOptions.PHASE, "train" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `trainMatch`** + +Enterprise convenience — combines `train` and `match` + +```python +options = ClientOptions([ ClientOptions.PHASE, "trainMatch" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `match`** + +```python +options = ClientOptions([ ClientOptions.PHASE, "match" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `link`** + +```python +options = ClientOptions([ ClientOptions.PHASE, "link" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `updateLabel`** + +Revisit and correct previously marked pairs. + +```python +options = ClientOptions([ ClientOptions.PHASE, "updateLabel" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +#### **Run `diff`** + +Compare two model outputs. + +```python +options = ClientOptions([ ClientOptions.PHASE, "diff" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +### Incremental matching with `IncrementalArguments` + +`runIncremental` requires `IncrementalArguments` instead of plain `EArguments`. It tracks new, changed, and deleted records, applies them to the existing identity graph, and writes the updated graph. + +#### Build incremental + +```python +incrArgs = IncrementalArguments() +incrArgs.setParentArgs(args) +``` + +#### Incremental data (new and changed records) + +```python +incrPipe = ECsvPipe("testFebrlIncr", "examples/febrl/test-incr.csv", schema) +incrArgs.setIncrementalData(incrPipe) +``` + +#### Optional - deleted data + +```python +deletedPipe = ECsvPipe("testFebrlDeleted", "examples/febrl/test-deleted.csv", schema) +incrArgs.setDeletedData(deletedPipe) +``` + +#### Optional - deleted action + +```python +incrArgs.setDeleteAction("HARD_DELETE") +``` + +#### Optional - temporary output directory + +```python +incrArgs.setOutputTmp("/tmp/zinggIncrTmp") +``` + +#### Execute + +```python +incrOptions = ClientOptions([ ClientOptions.PHASE, "runIncremental" ]) +zinggIncr = EZingg(incrArgs, incrOptions) +zinggIncr.initAndExecute() +``` + +{% hint style="success" icon="right-long" %} +For the full incremental matching workflow → [Run Incremental Matching](../running-zingg/run-incremental-matching.md) +{% endhint %} + +### Cluster approval with `ApproverArguments` + +`ApproverArguments` is the Enterprise workflow for human-reviewed cluster approval — where domain experts approve or reject specific clusters before they enter the production identity graph. + +```python +from zinggEC.enterprise.common.ApproverArguments import * +``` + +#### Build approver arguments + +```python +apprArgs = ApproverArguments() +apprArgs.setParentArgs(args) +``` + +#### Approval SQL query identifies clusters + +Requiring human review. + +```python +apprArgs.setApprovalQuery( + "SELECT * FROM clusters " + "WHERE confidence < 0.85") +``` + +#### Destination for approved clusters + +```python +destPipe = ECsvPipe("approved", "/tmp/approvedClusters") +apprArgs.setDestination(destPipe) +``` + +#### Execute approval phase + +```python +destPipe = ECsvPipe("approved", "/tmp/approvedClusters") +apprArgs.setDestination(destPipe) +apprOptions = ClientOptions([ ClientOptions.PHASE, "approve" ]) +zinggAppr = EZingg(apprArgs, apprOptions) +zinggAppr.initAndExecute() +``` + +{% hint style="success" icon="right-long" %} +**Read more**: For the full cluster approval workflow → [Cluster Approval](../running-zingg/cluster-approval.md) +{% endhint %} + +### Full example + +Complete working example combining all the above. + +```python +from zingg.client import * +from zingg.pipes import * +from zinggEC.enterprise.common.ApproverArguments import * +from zinggEC.enterprise.common.IncrementalArguments import * +from zinggEC.enterprise.common.MappingMatchType import * +from zinggEC.enterprise.common.epipes import * +from zinggEC.enterprise.common.EArguments import * +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition + +args = EArguments() + +recId = EFieldDefinition("recId", "string", MatchType.DONT_USE) +recId.setPrimaryKey(True) + +fname = EFieldDefinition("fname", "string", MatchType.FUZZY) +lname = EFieldDefinition("lname", "string", MatchType.FUZZY) +stNo = EFieldDefinition("stNo", "string", MatchType.FUZZY) +add1 = EFieldDefinition("add1", "string", MatchType.FUZZY) +add2 = EFieldDefinition("add2", "string", MatchType.FUZZY) +city = EFieldDefinition("city", "string", MatchType.FUZZY) +areacode = EFieldDefinition("areacode", "string", MatchType.FUZZY) +state = EFieldDefinition("state", "string", MatchType.FUZZY) +dob = EFieldDefinition("dob", "string", MatchType.FUZZY) +ssn = EFieldDefinition("ssn", "string", MatchType.FUZZY) + +fieldDefs = [ + recId, fname, lname, stNo, add1, add2, city, + areacode, state, dob, + ssn +] +args.setFieldDefinition(fieldDefs) + +args.setModelId("100") +args.setZinggDir("./models") +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.5) +args.setBlockingModel("DEFAULT") + +args.setPassthroughExpr("fname = 'matilda'") + +dm1 = DeterministicMatching('fname', 'stNo', 'add1') +dm2 = DeterministicMatching('ssn') +dm3 = DeterministicMatching('fname', 'stNo', 'lname') +args.setDeterministicMatchingCondition(dm1, dm2, dm3) + +schema = ( + "recId string, fname string, " + "lname string, stNo string, " + "add1 string, add2 string, " + "city string, areacode string, " + "state string, dob string, " + "ssn string" +) + +inputPipe = ECsvPipe("testFebrl", "examples/febrl/test.csv", schema) +args.setData(inputPipe) + +outputPipe = ECsvPipe("resultFebrl", "/tmp/febrlOutput") +outputPipe.setHeader("true") +args.setOutput(outputPipe) + +options = ClientOptions([ + ClientOptions.PHASE, + "trainMatch" +]) +zingg = EZingg(args, options) +zingg.initAndExecute() + +incrArgs = IncrementalArguments() +incrArgs.setParentArgs(args) + +incrPipe = ECsvPipe("testFebrlIncr", "examples/febrl/test-incr.csv", schema) +incrArgs.setIncrementalData(incrPipe) + +incrOptions = ClientOptions([ + ClientOptions.PHASE, + "runIncremental" +]) +zinggIncr = EZingg(incrArgs, incrOptions) +zinggIncr.initAndExecute() +``` + +{% hint style="success" icon="right-long" %} +**Read more**: Auto-generated API reference for every class and method: [Zingg Enterprise Common Python API on GitHub](https://github.com/zinggAI/zingg/blob/main/docs/pythonEC/markdown/zinggEC.md) +{% endhint %} diff --git a/docs/zingg-python-api/enterprise-zingges-python-api.md b/docs/zingg-python-api/enterprise-zingges-python-api.md new file mode 100644 index 000000000..52e257b7e --- /dev/null +++ b/docs/zingg-python-api/enterprise-zingges-python-api.md @@ -0,0 +1,160 @@ +--- +description: >- + Enterprise Plus Python API - the Spark client (EZingg, EZinggWithSpark) used + by Enterprise Plus Zingg distributions. +tags: + - tag: enterprise-only + primary: true +--- + +# Enterprise ZinggES Python API + +ZinggES is the Zingg Enterprise Spark Python package. It provides the Spark client classes - `EZingg` and `EZinggWithSpark` and used by Enterprise Plus distributions. The API is otherwise identical to ZinggEC. + +Use this when you have an Enterprise Plus licence. The arguments, field definitions, pipes, and execution pattern are the same as ZinggEC; only the import package and Spark client classes differ. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* For Enterprise (and Enterprise Lite) without the Spark client - [Enterprise ZinggEC Python API](enterprise-zinggec-python-api.md) +* For Community - [Community Python API](community-python-api.md) +{% endhint %} + +{% hint style="warning" icon="right-long" %} +ZinggES requires a Zingg Enterprise Plus licence and the `zinggES` package. [Contact Zingg to get access](https://www.zingg.ai/company/contact/contact) +{% endhint %} + +### Requirements + +* Python 3.6+ +* Spark 3.5.0 +* Zingg Enterprise Plus licence + +### Install the package + +```bash +pip install zinggES +``` + +### Modules and classes + +#### **`zinggES.enterprise.spark` package** + +
ModuleClassesPurpose
ESparkClientEZingg, EZinggWithSparkEnterprise Plus Spark execution clients
+ +`EZingg` is the standard Enterprise Plus client. `EZinggWithSpark` is for environments where a Spark session already exists (Databricks, Fabric notebooks) - it reuses that session instead of creating a new one. + +{% hint style="success" icon="right-long" %} +All ZinggEC classes (`EArguments`, `EFieldDefinition`, `ECsvPipe`, `UCPipe`, `IncrementalArguments`, `ApproverArguments`, `MappingMatchType`, etc) work identically with ZinggES. Only the Spark client class changes. +{% endhint %} + +### Imports + +```python +from zingg.client import * +from zingg.pipes import * +from zinggEC.enterprise.common.ApproverArguments import * +from zinggEC.enterprise.common.IncrementalArguments import * +from zinggEC.enterprise.common.MappingMatchType import * +from zinggEC.enterprise.common.epipes import * +from zinggEC.enterprise.common.EArguments import * +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition +from zinggES.enterprise.spark.ESparkClient import * +``` + +### Execute Zingg Enterprise Plus phases + +Use `EZingg` (or `EZinggWithSpark` in notebook environments where a Spark session exists). The pattern is the same as ZinggEC. + +#### **Run `trainMatch`** + +```python +options = ClientOptions([ ClientOptions.PHASE, "trainMatch" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +**Run any other phase** `findTrainingData`, `label`, `findAndLabel`, `generateDocs`, `train`, `match`, `link`, `updateLabel`, `diff` — by changing the phase name. See [Enterprise ZinggEC Python API](enterprise-zinggec-python-api.md) for the full list. + +### Using `EZinggWithSpark` in notebooks + +When running inside a Databricks, Fabric, or other notebook where a Spark session already exists, use `EZinggWithSpark`: + +```python +zingg = EZinggWithSpark(args, options) +zingg.initAndExecute() +``` + +`EZinggWithSpark` reuses the existing Spark session instead of creating a new one. Recommended for notebook environments. + +### Incremental matching + +```python +incrArgs = IncrementalArguments() +incrArgs.setParentArgs(args) +incrPipe = ECsvPipe("testFebrlIncr", "examples/febrl/test-incr.csv", schema) +incrArgs.setIncrementalData(incrPipe) + +incrOptions = ClientOptions([ ClientOptions.PHASE, "runIncremental" ]) +zinggIncr = EZingg(incrArgs, incrOptions) +zinggIncr.initAndExecute() +``` + +### Full example + +The full example matches the ZinggEC example exactly. The only difference is the `import` for `ESparkClient` and that the Spark client is invoked as `EZingg` from `zinggES.enterprise.spark.ESparkClient` instead of from `zinggEC`. + +```python +from zingg.client import * +from zingg.pipes import * +from zinggEC.enterprise.common.ApproverArguments import * +from zinggEC.enterprise.common.IncrementalArguments import * +from zinggEC.enterprise.common.MappingMatchType import * +from zinggEC.enterprise.common.epipes import * +from zinggEC.enterprise.common.EArguments import * +from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition +from zinggES.enterprise.spark.ESparkClient import * + +args = EArguments() + +recId = EFieldDefinition("recId", "string", MatchType.DONT_USE) +recId.setPrimaryKey(True) + +fname = EFieldDefinition("fname", "string", MatchType.FUZZY) +fieldDefs = [ recId, fname ] +args.setFieldDefinition(fieldDefs) + +args.setModelId("100") +args.setZinggDir("./models") +args.setNumPartitions(4) +args.setLabelDataSampleSize(0.5) +args.setBlockingModel("DEFAULT") +args.setPassthroughExpr("fname = 'matilda'") + +dm1 = DeterministicMatching('fname', 'stNo', 'add1') +args.setDeterministicMatchingCondition(dm1) + +schema = ( + "recId string, fname string, " + "lname string" +) + +inputPipe = ECsvPipe("testFebrl", "examples/febrl/test.csv", schema) +args.setData(inputPipe) + +outputPipe = ECsvPipe("resultFebrl", "/tmp/febrlOutput") +outputPipe.setHeader("true") +args.setOutput(outputPipe) + +options = ClientOptions([ ClientOptions.PHASE, "trainMatch" ]) +zingg = EZingg(args, options) +zingg.initAndExecute() +``` + +{% hint style="success" icon="right-long" %} +**Read more**: The full example shown on the [ZinggEC Python API reference](https://docs.zingg.ai/latest/working-with-python/working-with-python-enterprise/index) and the [ZinggES Python API reference](https://docs.zingg.ai/latest/working-with-python/working-with-python-enterprise/index-1) is identical, except for which Spark client package is imported. +{% endhint %} + +{% hint style="info" icon="right-long" %} +Auto-generated API reference for `ESparkClient`, `EZingg`, `EZinggWithSpark`: [Zingg Enterprise Spark Python API on GitHub](https://github.com/zinggAI/zingg/blob/main/docs/pythonES/markdown/ESparkClient.md). +{% endhint %} diff --git a/docs/zingg-python-api/working-with-python.md b/docs/zingg-python-api/working-with-python.md new file mode 100644 index 000000000..75c149e27 --- /dev/null +++ b/docs/zingg-python-api/working-with-python.md @@ -0,0 +1,45 @@ +--- +description: >- + Build and run Zingg entity resolution programs in Python. Available in + Community, Enterprise (ZinggEC), and Enterprise Plus (ZinggES). +--- + +# Working with Python + +Instead of configuring Zingg using JSON, you can use Python to build and run Zingg entity and identity resolution programs. This is the recommended approach when you are running Zingg on an existing Spark cluster like Databricks, Microsoft Fabric, AWS EMR, or Glue. + +The Python API is available in three editions, each with its own package and class names. The pattern is the same across all three: set up `args`, set `options` with the phase name, create the Zingg object, call `initAndExecute()`. + +{% hint style="success" icon="right-long" %} +To run on a local machine, install Zingg from the release before running Zingg Python programs. The Python package can be installed via `pip install zingg`. Detailed auto-generated API documentation is available at [readthedocs.org/projects/zingg](https://readthedocs.org/projects/zingg/). +{% endhint %} + +#### Choose your edition + +
EditionPackageUse when
Community (Open Source)zinggFree, open-source Zingg for any Spark deployment
Enterprise (ZinggEC)zinggECEnterprise Lite or Enterprise — adds deterministic matching, blocking strategy, primary key, pass-through, mapping match types, incremental, output stats
Enterprise Plus (ZinggES)zinggESAdds the Spark client (EZingg, EZinggWithSpark) used by Enterprise Plus distributions
+ +{% hint style="success" icon="right-long" %} +**Read more**: Pick your edition and follow the sub-page below: + +* [Community Python API](community-python-api.md) +* [Enterprise ZinggEC Python API](enterprise-zinggec-python-api.md) +* [Enterprise ZinggES Python API](enterprise-zingges-python-api.md) +{% endhint %} + +### Common pattern across all editions + +Every Zingg Python program regardless of edition, follows the same five-step pattern: + +1. **Imports** - import Zingg client and pipes modules +2. **Build `args`** - create the arguments object (`Arguments` in Community, `EArguments` in Enterprise) +3. **Set fields** - create field definitions and register with `args.setFieldDefinition(...)` +4. **Set input and output pipes** - configure data sources and sinks +5. **Set options and execute** - create `ClientOptions` with the phase name, instantiate the Zingg object, call `initAndExecute()` + +{% hint style="success" icon="right-long" %} +**Read more**: + +* [Configure Zingg](../running-zingg/configure-zingg.md) - the configuration that feeds into every Python API call +* [Run Matches or Link](../running-zingg/run-the-match-phase.md) - running the match and link phases +* [Zingg Command Line](../reference/zingg-command-line.md) - CLI alternative to the Python API +{% endhint %} From b9f9d0df4ccebec33504b7b5eef84ee846c47f2d Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Tue, 11 Aug 2026 15:36:20 +0530 Subject: [PATCH 015/144] Fix typos in compare-model-results doc Correct typos in docs/running-zingg/compare-model-results.md: change 'Snowfalke' to 'Snowflake' and 'provivded' to 'provided' in the Enterprise Snowflake tab. --- docs/running-zingg/compare-model-results.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/running-zingg/compare-model-results.md b/docs/running-zingg/compare-model-results.md index 2b913f964..9c7dabcd9 100644 --- a/docs/running-zingg/compare-model-results.md +++ b/docs/running-zingg/compare-model-results.md @@ -117,8 +117,8 @@ zinggDiff.initAndExecute() ``` {% endtab %} -{% tab title="Enterprise for Snowfalke" %} -_**CHECK WITH SONAL - Enterprise Snowflake content for this topic to be provivded by Sonal**_ +{% tab title="Enterprise for Snowflake" %} +_**Enterprise Snowflake content for this topic to be provided by Sonal**_ {% endtab %} {% endtabs %} From 4ee4e9947b1f86f619012a2c56b2627ea487fd6f Mon Sep 17 00:00:00 2001 From: Aditya Pareek <119134401+LOGANBLUE1@users.noreply.github.com> Date: Tue, 11 Aug 2026 17:42:07 +0530 Subject: [PATCH 016/144] added snowflake steps for configuring zingg --- docs/running-zingg/configure-zingg.md | 146 +++++++++++++++++++++++++- 1 file changed, 145 insertions(+), 1 deletion(-) diff --git a/docs/running-zingg/configure-zingg.md b/docs/running-zingg/configure-zingg.md index 465d3cf97..02f776909 100644 --- a/docs/running-zingg/configure-zingg.md +++ b/docs/running-zingg/configure-zingg.md @@ -482,6 +482,150 @@ args.setPassthroughExpr("fname = 'matilda'") {% endtab %} {% tab title="Enterprise Snowflake" %} -**CHECK WITH SONAL ABOUT THIS TOPIC - NEEDS ENTIRELY DIFFERENT SET OF CONTENT TO BE DISCUSSED LATER.** + +### Step 1: Build the Enterprise arguments object + + +Set the blocking strategy directly after `setLabelDataSampleSize`. If not set, the model follows `DEFAULT`. + +#### JSON + +```json +{ + "modelId": "100", + "zinggDir": "/tmp/models", + "numPartitions": 4, + "labelDataSampleSize": 0.5 +} +``` + +### Step 2: Define fields with `EFieldDefinition` + +#### JSON + +```json + "fieldDefinition":[ + { + "fieldName" : "recid", + "matchType" : "dont_use", + "fields" : "recid", + "dataType": "string" , + "primaryKey": "true" + }, + { + "fieldName" : "givenname", + "matchType" : "fuzzy", + "fields" : "givenname", + "dataType": "string" + }, + { + "fieldName" : "surname", + "matchType": "exact", + "fields" : "surname", + "dataType": "string" + }, + { + "fieldName" : "suburb", + "matchType": "fuzzy", + "fields" : "suburb", + "dataType": "string" + }, + { + "fieldName" : "postcode", + "matchType": "exact", + "fields" : "postcode", + "dataType": "string" + } + ] +``` + +{% hint style="info" icon="right-long" %} +Enterprise requires a primary key field for `runIncremental`. Mark the primary key field by calling `recId.setPrimaryKey(True)` if you plan to use incremental matching. +{% endhint %} + +### Step 3: Configure input and output pipes + +#### JSON + +```json + "data" : [{ + "name":"test", + "format":"csv", + "props": { + "location": "examples/ncVoters5M/5Party-ocp20/", + "delimiter": ",", + "header":false + }, + "schema": "recid string, givenname string, surname string, suburb string, postcode string" + }], + "outputStats" : { + "name":"stats", + "format":"csv", + "props": { + "location": "/tmp/zinggStats_$ZINGG_DYNAMIC_STAT_NAME", + "delimiter": ",", + "header":true + } + } +``` + +### Step 4: Deterministic matching (optional) + +{% hint style="info" icon="right-long" %} +Deterministic matching - **Enterprise** only. + +Skip this step if you only need probabilistic matching. +{% endhint %} + +#### JSON + +```json +{ + "deterministicMatching": [ + {"matchCondition": [ + {"fieldName": "fname"}, + {"fieldName": "stNo"}, + {"fieldName": "add1"} + ]}, + {"matchCondition": [ + {"fieldName": "fname"}, + {"fieldName": "dob"}, + {"fieldName": "ssn"} + ]}, + {"matchCondition": [ + {"fieldName": "fname"}, + {"fieldName": "email"} + ]} + ] +} +``` + +### Step 5: Pass Through (optional) + +{% hint style="info" icon="right-long" %} +Pass Through - **Enterprise** only. + +Excludes specific records from matching while still including them in output with their own `Zingg ID`. +{% endhint %} + +Pass Through excludes specific records from matching while still including them in output with their own Zingg ID. Records matching the passthrough expression appear in the identity graph but never influence cluster formation. + +**Note**: Zingg internally applies the negation of `passthroughExpr` to filter matching records. If the passthrough condition applies to nullable fields, ensure the negative of the expression yields the records that are `NOT` passthrough. + +#### JSON + +```json +{ + "passthroughExpr": "fname = 'matilda'" +} +``` + +#### Example with null-safe expression + +```json +{ + "passthroughExpr": "is_deceased = true AND is_deceased is NOT NULL" +} +``` {% endtab %} {% endtabs %} From cf23d8e1dab5ef8146a8fd1a4451428b35019274 Mon Sep 17 00:00:00 2001 From: Aditya Pareek <119134401+LOGANBLUE1@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:23:15 +0530 Subject: [PATCH 017/144] added snowflake steps --- .../running-zingg/build-and-save-the-model.md | 15 ++++- docs/running-zingg/configure-zingg.md | 61 ++++++++++++------- docs/running-zingg/create-training-data.md | 30 ++++++++- .../generate-model-documentation.md | 9 ++- docs/running-zingg/label-training-pairs.md | 14 ++++- docs/running-zingg/verify-blocking.md | 8 ++- 6 files changed, 111 insertions(+), 26 deletions(-) diff --git a/docs/running-zingg/build-and-save-the-model.md b/docs/running-zingg/build-and-save-the-model.md index f45752e59..4cefb412e 100644 --- a/docs/running-zingg/build-and-save-the-model.md +++ b/docs/running-zingg/build-and-save-the-model.md @@ -67,6 +67,19 @@ Available options: Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark cluster. {% endhint %} -_**CHECK WITH SONAL ABOUT THE ENTIRE SNOWFLAKE RELATED CONTENT**_ + +### CLI + +```bash +./zingg.sh --phase train --conf config.json +``` + +Enterprise adds blocking model configuration via `args.setBlockingModel()`. Set this in Configure Zingg before running `train`. + +Available options: + +* `DEFAULT` - standard blocking strategy, suitable for most datasets +* `WIDER` - casts a wider blocking net; use if known matching pairs are being missed + {% endtab %} {% endtabs %} diff --git a/docs/running-zingg/configure-zingg.md b/docs/running-zingg/configure-zingg.md index 02f776909..48b8acb1c 100644 --- a/docs/running-zingg/configure-zingg.md +++ b/docs/running-zingg/configure-zingg.md @@ -540,7 +540,7 @@ Set the blocking strategy directly after `setLabelDataSampleSize`. If not set, t ``` {% hint style="info" icon="right-long" %} -Enterprise requires a primary key field for `runIncremental`. Mark the primary key field by calling `recId.setPrimaryKey(True)` if you plan to use incremental matching. +Enterprise requires a primary key field for `runIncremental`. Mark the primary key field by adding `"primaryKey": "true"` if you plan to use incremental matching. {% endhint %} ### Step 3: Configure input and output pipes @@ -548,25 +548,20 @@ Enterprise requires a primary key field for `runIncremental`. Mark the primary k #### JSON ```json - "data" : [{ - "name":"test", - "format":"csv", - "props": { - "location": "examples/ncVoters5M/5Party-ocp20/", - "delimiter": ",", - "header":false - }, - "schema": "recid string, givenname string, surname string, suburb string, postcode string" - }], - "outputStats" : { - "name":"stats", - "format":"csv", - "props": { - "location": "/tmp/zinggStats_$ZINGG_DYNAMIC_STAT_NAME", - "delimiter": ",", - "header":true - } - } + "output" : [{ + "name":"output_Febrl_5M", + "format":"snowflake", + "props": { + "table": "OUTPUT_Febrl_5M" + } + }], + "data" : [{ + "name":"FEBRL5M", + "format":"snowflake", + "props": { + "table": "Febrl_5M" + } + }], ``` ### Step 4: Deterministic matching (optional) @@ -600,7 +595,31 @@ Skip this step if you only need probabilistic matching. } ``` -### Step 5: Pass Through (optional) +### Step 5: Output stats (optional) + +{% hint style="info" icon="right-long" %} +Output stats - **Enterprise** only. + +Skip this step if you do not need run statistics. +{% endhint %} + +Configures where Zingg writes statistics for the `match` and `incrementalRun` phases. The `$ZINGG_DYNAMIC_STAT_NAME` placeholder is substituted at runtime with `SUMMARY`, `CLUSTER`, or `RECORD`, so each run writes the three statistics tables separately. If `outputStats` is not configured, Zingg skips stats writing and the run proceeds normally. + +#### JSON + +```json + "outputStats" : { + "name":"stats", + "format":"snowflake", + "props": { + "table": "zinggStats_$ZINGG_DYNAMIC_STAT_NAME2" + } + }, +``` + +**Read more:** For the fields in each statistics table → [Output Statistics](../interpreting-results/output-statistics.md). + +### Step 6: Pass Through (optional) {% hint style="info" icon="right-long" %} Pass Through - **Enterprise** only. diff --git a/docs/running-zingg/create-training-data.md b/docs/running-zingg/create-training-data.md index 8589e9f9a..f73f7ee6c 100644 --- a/docs/running-zingg/create-training-data.md +++ b/docs/running-zingg/create-training-data.md @@ -103,6 +103,34 @@ Candidate pairs are written to `zinggDir/modelId`. Run the label phase next to r {% endtab %} {% tab title="Enterprise Snowflake" %} -**CHECK WITH SONAL ABOUT THIS TOPIC - NEEDS ENTIRELY DIFFERENT SET OF CONTENT TO BE DISCUSSED LATER.** +### Set label data sample size + +#### JSON + +```json + "labelDataSampleSize" : 0.5, +``` + +### Run findTrainingData + +#### JSON + +```json +./zingg.sh --phase findTrainingData --conf config.json \ +--properties-file +``` + +Enterprise Snowflake also provides the `findAndLabel` combined phase, which runs `findTrainingData` and `label` together in a single call. Use this for smaller datasets where `findTrainingData` completes quickly: + + +{% hint style="info" icon="right-long" %} +Candidate pairs are written to `zinggDir/modelId`. Run the label phase next to review and label these pairs. +{% endhint %} + {% endtab %} {% endtabs %} diff --git a/docs/running-zingg/generate-model-documentation.md b/docs/running-zingg/generate-model-documentation.md index a994dc5c6..5f2623260 100644 --- a/docs/running-zingg/generate-model-documentation.md +++ b/docs/running-zingg/generate-model-documentation.md @@ -62,7 +62,14 @@ zingg.initAndExecute() {% endtab %} {% tab title="Enterprise Snowflake" %} -**CHECK WITH SONAL ABOUT THIS TOPIC - NEEDS ENTIRELY DIFFERENT SET OF CONTENT TO BE DISCUSSED LATER.** + +### CLI + +```bash +./scripts/zingg.sh --phase generateDocs --conf \ + --showConcise=true --properties-file +``` + {% endtab %} {% endtabs %} diff --git a/docs/running-zingg/label-training-pairs.md b/docs/running-zingg/label-training-pairs.md index 8b57ebe44..f7222662b 100644 --- a/docs/running-zingg/label-training-pairs.md +++ b/docs/running-zingg/label-training-pairs.md @@ -103,6 +103,18 @@ _**CHECK WITH SONAL - Please confirm the exact Python code for the interactive l {% endtab %} {% tab title="Enterprise Snowflake" %} -**CHECK WITH SONAL ABOUT THIS TOPIC - NEEDS ENTIRELY DIFFERENT SET OF CONTENT TO BE DISCUSSED LATER.** +The `showConcise` flag only shows fields which are `NOT DONT_USE`. This makes the labelling session cleaner when you have many fields. + +### Run label + +#### JSON + +```json +./zingg.sh --phase label --conf config.json --showConcise=true \ --properties-file +``` + +{% hint style="info" icon="right-long" %} +Enterprise provides a visual widget showing one pair at a time with Match, No Match, and Can't Say buttons. Download the diagnostics view to share match quality with stakeholders before committing to training. +{% endhint %} {% endtab %} {% endtabs %} diff --git a/docs/running-zingg/verify-blocking.md b/docs/running-zingg/verify-blocking.md index 4fb5fbfa0..0460d7664 100644 --- a/docs/running-zingg/verify-blocking.md +++ b/docs/running-zingg/verify-blocking.md @@ -59,7 +59,13 @@ zingg.initAndExecute() {% endtab %} {% tab title="Enterprise Snowflake" %} -**CHECK WITH SONAL ABOUT THIS TOPIC - NEEDS ENTIRELY DIFFERENT SET OF CONTENT TO BE DISCUSSED LATER.** + +### CLI + +```bash +./scripts/zingg.sh --phase verifyBlocking --conf config.json \ +--properties-file +``` {% endtab %} {% endtabs %} From f24de49e1719b9b0c318602a5173360ae34af798 Mon Sep 17 00:00:00 2001 From: Aditya Pareek <119134401+LOGANBLUE1@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:22:15 +0530 Subject: [PATCH 018/144] remaining snowflake instructions --- .../running-zingg/build-and-save-the-model.md | 3 +- docs/running-zingg/create-training-data.md | 2 +- docs/running-zingg/label-training-pairs.md | 2 +- docs/running-zingg/link-across-datasets.md | 12 +++++- docs/running-zingg/reassign-zingg-id.md | 43 +++++++++++++++++-- .../running-zingg/run-incremental-matching.md | 37 +++++++++++++++- docs/running-zingg/run-the-match-phase.md | 13 +++++- 7 files changed, 102 insertions(+), 10 deletions(-) diff --git a/docs/running-zingg/build-and-save-the-model.md b/docs/running-zingg/build-and-save-the-model.md index 4cefb412e..354b5ca67 100644 --- a/docs/running-zingg/build-and-save-the-model.md +++ b/docs/running-zingg/build-and-save-the-model.md @@ -71,7 +71,8 @@ Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark c ### CLI ```bash -./zingg.sh --phase train --conf config.json +./zingg.sh --phase train --conf config.json \ +--properties-file ``` Enterprise adds blocking model configuration via `args.setBlockingModel()`. Set this in Configure Zingg before running `train`. diff --git a/docs/running-zingg/create-training-data.md b/docs/running-zingg/create-training-data.md index f73f7ee6c..92d1ddf41 100644 --- a/docs/running-zingg/create-training-data.md +++ b/docs/running-zingg/create-training-data.md @@ -113,7 +113,7 @@ Candidate pairs are written to `zinggDir/modelId`. Run the label phase next to r ### Run findTrainingData -#### JSON +#### CLI ```json ./zingg.sh --phase findTrainingData --conf config.json \ diff --git a/docs/running-zingg/label-training-pairs.md b/docs/running-zingg/label-training-pairs.md index f7222662b..080e05147 100644 --- a/docs/running-zingg/label-training-pairs.md +++ b/docs/running-zingg/label-training-pairs.md @@ -107,7 +107,7 @@ The `showConcise` flag only shows fields which are `NOT DONT_USE`. This makes th ### Run label -#### JSON +#### CLI ```json ./zingg.sh --phase label --conf config.json --showConcise=true \ --properties-file diff --git a/docs/running-zingg/link-across-datasets.md b/docs/running-zingg/link-across-datasets.md index 132485dd2..ac5cfef78 100644 --- a/docs/running-zingg/link-across-datasets.md +++ b/docs/running-zingg/link-across-datasets.md @@ -100,7 +100,17 @@ Enterprise link output includes Zingg ID instead of `Z_CLUSTER`, plus the `Z_SOU Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark cluster. {% endhint %} -_**CHECK WITH SONAL - Enterprise Snowflake content for this topic to be provivded by Sonal**_ +### CLI + +```bash +./zingg.sh --phase link --conf config.json \ +--properties-file +``` + +{% hint style="info" icon="right-long" %} +Enterprise link output includes Zingg ID instead of `Z_CLUSTER`, plus the `Z_SOURCE` column identifying the source dataset of each record. +{% endhint %} + {% endtab %} {% endtabs %} diff --git a/docs/running-zingg/reassign-zingg-id.md b/docs/running-zingg/reassign-zingg-id.md index d62ac4d0d..72062d8ff 100644 --- a/docs/running-zingg/reassign-zingg-id.md +++ b/docs/running-zingg/reassign-zingg-id.md @@ -184,7 +184,7 @@ zinggReassign.initAndExecute() {% endtab %} {% tab title="Enterprise JSON" %} -### **Configuration wrapper (`configReassign.json`)** +### **Configuration wrapper (`reassignConf.json`)** The JSON wrapper config references your new model config and specifies the destination for the reassigned output. @@ -203,7 +203,7 @@ The JSON wrapper config references your new model config and specifies the desti } ``` -* `--conf` - the wrapper config (`configReassign.json`) that references your new model +* `--conf` - the wrapper config (`reassignConf.json`) that references your new model * `--originalZinggId` - your original production configuration * `--properties-file` - optional Zingg properties file @@ -212,7 +212,7 @@ The CLI command takes three arguments: ```bash ./scripts/zingg.sh \ --phase reassignZinggId \ - --conf examples/febrl/sparkIncremental/configReassign5M.json \ + --conf \ --originalZinggId examples/febrl5M/config.json \ --properties-file config/zingg.conf ``` @@ -223,6 +223,41 @@ The `--originalZinggId` flag takes the path to your original production config f {% endtab %} {% tab title="Enterprise Snowflake" %} -_**CHECK WITH SONAL - Enterprise Snowflake content for this topic to be provivded by Sonal**_ + +### **Configuration wrapper (`reassignConf.json`)** + +The JSON wrapper config references your new model config and specifies the destination for the reassigned output. + +```json +{ + "config": "configUpdated.json", + "transformedOutputPath": { + "name": "reassignedOutput", + "format":"snowflake", + "props": { + "table": "OUTPUT_REASSIGN_Febrl_5M" + } + } +} +``` + +* `--conf` - the wrapper config (`reassignConf.json`) that references your new model +* `--originalZinggId` - your original production configuration +* `--properties-file` - optional Zingg properties file + +The CLI command takes three arguments: + +```bash +./scripts/zingg.sh \ + --phase reassignZinggId \ + --conf \ + --originalZinggId examples/febrl5M/config.json \ + --properties-file ./snowflakeConn.properties +``` + +{% hint style="info" icon="right-long" %} +The `--originalZinggId` flag takes the path to your original production config file. Despite the flag name, you are passing the **configuration file** that defined the original model - Zingg uses that config to locate the original output containing the Zingg IDs to be preserved. +{% endhint %} + {% endtab %} {% endtabs %} diff --git a/docs/running-zingg/run-incremental-matching.md b/docs/running-zingg/run-incremental-matching.md index ba16d1cc9..3f3577a02 100644 --- a/docs/running-zingg/run-incremental-matching.md +++ b/docs/running-zingg/run-incremental-matching.md @@ -226,6 +226,41 @@ The `outputTmp` section specifies a temporary output location where Zingg writes {% endtab %} {% tab title="Enterprise Snowflake" %} -_**CHECK WITH SONAL - Enterprise Snowflake content for this topic to be provivded by Sonal**_ + +Create an `incrementalConf.json` file: + +```json +{ + "config" : "configSnow.json", + "incrementalData": [{ + "name":"INCR_Febrl_5M", + "format":"snowflake", + "props": { + "table": "INCR_Febrl_5M" + }, + "schema": "id string, fname string, lname string, stNo string, add1 string, add2 string, city string, state string, areacode string, dob string, ssn string" + }], + "outputTmp" :{ + "name":"customers_incr_temp", + "format":"snowflake", + "props": { + "table": "customers_incr_temp" + } + } +} +``` + +**Run With** + +```bash +./scripts/zingg.sh \ + --phase runIncremental \ + --conf \ + --properties-file ./snowflakeConn.properties +``` + +The `outputTmp` section specifies a temporary output location where Zingg writes intermediate results before final processing or merging with the main output. +{% endtab %} + {% endtab %} {% endtabs %} diff --git a/docs/running-zingg/run-the-match-phase.md b/docs/running-zingg/run-the-match-phase.md index 7c7ad4cdc..e86978149 100644 --- a/docs/running-zingg/run-the-match-phase.md +++ b/docs/running-zingg/run-the-match-phase.md @@ -101,7 +101,18 @@ Enterprise output includes `Zingg ID` (persistent across runs) instead of `Z_CLU Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark cluster. {% endhint %} -_**CHECK WITH SONAL - Enterprise Snowflake content for this topic to be provivded by Sonal**_ + +### CLI + +```bash +./zingg.sh --phase match --conf config.json \ +--properties-file +``` + +{% hint style="info" icon="right-long" %} +Enterprise output includes `Zingg ID` (persistent across runs) instead of `Z_CLUSTER`, plus deterministic match flag and Match Statistics. `Zingg ID` is stable across all subsequent incremental runs. +{% endhint %} + {% endtab %} {% endtabs %} From 76b1aee5114f5dd4ea7a6695e138690d457ad9ed Mon Sep 17 00:00:00 2001 From: Aditya Pareek <119134401+LOGANBLUE1@users.noreply.github.com> Date: Thu, 13 Aug 2026 11:22:52 +0530 Subject: [PATCH 019/144] corrected mistakes --- docs/running-zingg/build-and-save-the-model.md | 2 +- docs/running-zingg/configure-zingg.md | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/running-zingg/build-and-save-the-model.md b/docs/running-zingg/build-and-save-the-model.md index 354b5ca67..df6fceb2f 100644 --- a/docs/running-zingg/build-and-save-the-model.md +++ b/docs/running-zingg/build-and-save-the-model.md @@ -75,7 +75,7 @@ Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark c --properties-file ``` -Enterprise adds blocking model configuration via `args.setBlockingModel()`. Set this in Configure Zingg before running `train`. +Enterprise adds blocking model configuration via `"modelId": "FEBRL120K"`. Set this in Configure Zingg before running `train`. Available options: diff --git a/docs/running-zingg/configure-zingg.md b/docs/running-zingg/configure-zingg.md index 48b8acb1c..26eb37a9d 100644 --- a/docs/running-zingg/configure-zingg.md +++ b/docs/running-zingg/configure-zingg.md @@ -483,10 +483,7 @@ args.setPassthroughExpr("fname = 'matilda'") {% tab title="Enterprise Snowflake" %} -### Step 1: Build the Enterprise arguments object - - -Set the blocking strategy directly after `setLabelDataSampleSize`. If not set, the model follows `DEFAULT`. +### Step 1: Build the Enterprise arguments #### JSON @@ -499,7 +496,7 @@ Set the blocking strategy directly after `setLabelDataSampleSize`. If not set, t } ``` -### Step 2: Define fields with `EFieldDefinition` +### Step 2: Define fields definitions #### JSON @@ -603,7 +600,7 @@ Output stats - **Enterprise** only. Skip this step if you do not need run statistics. {% endhint %} -Configures where Zingg writes statistics for the `match` and `incrementalRun` phases. The `$ZINGG_DYNAMIC_STAT_NAME` placeholder is substituted at runtime with `SUMMARY`, `CLUSTER`, or `RECORD`, so each run writes the three statistics tables separately. If `outputStats` is not configured, Zingg skips stats writing and the run proceeds normally. +Configures where Zingg writes statistics for the `match` and `runIncremental` phases. The `$ZINGG_DYNAMIC_STAT_NAME` placeholder is substituted at runtime with `SUMMARY`, `CLUSTER`, or `RECORD`, so each run writes the three statistics tables separately. If `outputStats` is not configured, Zingg skips stats writing and the run proceeds normally. #### JSON @@ -612,7 +609,7 @@ Configures where Zingg writes statistics for the `match` and `incrementalRun` ph "name":"stats", "format":"snowflake", "props": { - "table": "zinggStats_$ZINGG_DYNAMIC_STAT_NAME2" + "table": "zinggStats_$ZINGG_DYNAMIC_STAT_NAME" } }, ``` From 96df6f948ec76148837d1d661fb0d7edc9831db4 Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Thu, 13 Aug 2026 14:03:15 +0530 Subject: [PATCH 020/144] Use CompareArguments in compare/reassign docs Replace TransformedOutputArguments with CompareArguments in docs/running-zingg/compare-model-results.md and reassign-zingg-id.md. Update API calls (setOriginalArgs -> setCompareToArgs, setTransformedOutputPath -> setResults) and adjust example usage. Rename tab to "Enterprise Python" and add an "Enterprise CLI" tab. Small example tweaks: change sample CSV path and add a note about marking the primary key for reassignment. --- docs/running-zingg/compare-model-results.md | 17 ++++++++-------- docs/running-zingg/reassign-zingg-id.md | 22 +++++++++------------ 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/docs/running-zingg/compare-model-results.md b/docs/running-zingg/compare-model-results.md index 9c7dabcd9..7a4e3d0ac 100644 --- a/docs/running-zingg/compare-model-results.md +++ b/docs/running-zingg/compare-model-results.md @@ -52,16 +52,14 @@ The `diff` phase then identifies: * Merged or split clusters between the two models {% tabs %} -{% tab title="Enterprise" %} -### Python - +{% tab title="Enterprise Python" %} ```python from zingg.client import* from zingg.pipes import* from zinggEC.enterprise.common.EArguments import* from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition from zinggES.enterprise.spark.ESparkClient import* -from zinggEC.enterprise.common.TransformedOutputArguments import* +from zinggEC.enterprise.common.CompareArguments import* from zinggEC.enterprise.common.EClientOptions import* ``` @@ -83,16 +81,16 @@ newArgs.setModelId("200") newArgs.setZinggDir("./models") ``` -#### Step 3: Create TransformedOutputArguments +#### Step 3: Create CompareArguments ```python -diffArgs = TransformedOutputArguments() +diffArgs = CompareArguments() diffArgs.setParentArgs(newArgs) -diffArgs.setOriginalArgs(originalArgs) +diffArgs.setCompareToArgs(originalArgs) diffOutputPipe = ECsvPipe("diffOutput", "/tmp/zinggDiff") diffOutputPipe.setHeader("true") -diffArgs.setTransformedOutputPath(diffOutputPipe) +diffArgs.setResults(diffOutputPipe) ``` #### Step 4: Execute diff @@ -106,8 +104,9 @@ zinggDiff = EZingg(diffArgs, diffOptions) zinggDiff.initAndExecute() ``` -### CLI +{% endtab %} +{% tab title="Enterprise CLI" %} ```bash ./scripts/zingg.sh \ --phase diff \ diff --git a/docs/running-zingg/reassign-zingg-id.md b/docs/running-zingg/reassign-zingg-id.md index d62ac4d0d..6af34692a 100644 --- a/docs/running-zingg/reassign-zingg-id.md +++ b/docs/running-zingg/reassign-zingg-id.md @@ -95,17 +95,12 @@ from zingg.pipes import* from zinggEC.enterprise.common.EArguments import* from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition from zinggES.enterprise.spark.ESparkClient import* -from zinggEC.enterprise.common.TransformedOutputArguments import* +from zinggEC.enterprise.common.CompareArguments import* from zinggEC.enterprise.common.EClientOptions import* originalArgs = EArguments() - -id = EFieldDefinition("id", "string", MatchType.DONT_USE) -id.setPrimaryKey(True) -fname = EFieldDefinition("fname", "string", MatchType.FUZZY) -lname = EFieldDefinition("lname", "string", MatchType.FUZZY) - -originalArgs.setFieldDefinition([id, fname, lname]) +#... your original field definitions... +# Mark exactly one field as the primary key, e.g. id.setPrimaryKey(True) - reassign matches clusters using it originalArgs.setModelId("107") originalArgs.setZinggDir("./models") originalArgs.setNumPartitions(4) @@ -120,7 +115,7 @@ schema = ( originalInputPipe = ECsvPipe( "originalData", - "examples/febrl5M/febrl_data.csv", + "examples/febrl/test.csv", schema ) originalArgs.setData(originalInputPipe) @@ -138,6 +133,7 @@ Set up `EArguments` for your new model. This is the model that produced the new ```python newArgs = EArguments() +#... your updated field definitions... newArgs.setModelId("999") newArgs.setZinggDir("./models") ``` @@ -157,18 +153,18 @@ zinggNew = EZingg(newArgs, options) zinggNew.initAndExecute() ``` -### **Step 4: Create `TransformedOutputArguments`** +### **Step 4: Create `CompareArguments`** -`TransformedOutputArguments` wraps both configurations and the destination for the reassigned output. +`CompareArguments` wraps both configurations and the destination for the reassigned output. ```python -reassignArgs = TransformedOutputArguments() +reassignArgs = CompareArguments() reassignArgs.setParentArgs(newArgs) reassignArgs.setOriginalArgs(originalArgs) reassignOutputPipe = ECsvPipe("reassignedOutput", "/tmp/zinggReassigned") reassignOutputPipe.setHeader("true") -reassignArgs.setTransformedOutputPath(reassignOutputPipe) +reassignArgs.setResults(reassignOutputPipe) ``` ### Step 5: Execute `reassignZinggId` From 08a74a42f5a559fed5e489c518eb6c710a117e81 Mon Sep 17 00:00:00 2001 From: Aditya Pareek <119134401+LOGANBLUE1@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:19:29 +0530 Subject: [PATCH 021/144] addressed comments --- docs/running-zingg/create-training-data.md | 5 +---- docs/running-zingg/generate-model-documentation.md | 6 ++---- docs/running-zingg/label-training-pairs.md | 2 +- docs/running-zingg/reassign-zingg-id.md | 7 +------ docs/running-zingg/run-incremental-matching.md | 2 +- 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/docs/running-zingg/create-training-data.md b/docs/running-zingg/create-training-data.md index 92d1ddf41..2fe3e2822 100644 --- a/docs/running-zingg/create-training-data.md +++ b/docs/running-zingg/create-training-data.md @@ -95,8 +95,6 @@ zingg.initAndExecute() ./zingg.sh --phase findTrainingData --conf config.json ``` -Enterprise also provides the `findAndLabel` combined phase, which runs `findTrainingData` and `label` together in a single call. Use this for smaller datasets where `findTrainingData` completes quickly: - {% hint style="info" icon="right-long" %} Candidate pairs are written to `zinggDir/modelId`. Run the label phase next to review and label these pairs. {% endhint %} @@ -115,12 +113,11 @@ Candidate pairs are written to `zinggDir/modelId`. Run the label phase next to r #### CLI -```json +```bash ./zingg.sh --phase findTrainingData --conf config.json \ --properties-file ``` -Enterprise Snowflake also provides the `findAndLabel` combined phase, which runs `findTrainingData` and `label` together in a single call. Use this for smaller datasets where `findTrainingData` completes quickly: +The `snowflake.properties` file supplies the connection details Zingg needs to reach your Snowflake account and run against your warehouse from outside Snowflake. {% hint style="info" icon="right-long" %} Candidate pairs are written to `zinggDir/modelId`. Run the label phase next to review and label these pairs. {% endhint %} +#### Snowflake (job service) + +Run the phase as an asynchronous job service inside your Snowflake compute pool. + +```sql +EXECUTE JOB SERVICE +IN COMPUTE POOL CONTAINER_ZINGG_POOL +NAME = ZINGG_FTD_ASYNC_JOB_SERVICE +ASYNC = true +EXTERNAL_ACCESS_INTEGRATIONS = (ALLOW_ALL_EAI) +FROM @specs SPECIFICATION_TEMPLATE_FILE = '' +USING (PHASE => 'findTrainingData', CONFIG => ''); +``` + +The specification file passed via `SPECIFICATION_TEMPLATE_FILE` defines the container and job service configuration Zingg needs to run inside Snowpark Container Services. It is maintained by your administrator and referenced from your `@specs` stage. + +`` is the name of your Zingg configuration json file. + +**Monitor the job** + +Retrieve the service logs to monitor execution and debug any failures. +```sql +SELECT SYSTEM$GET_SERVICE_LOGS('ZINGG_FTD_ASYNC_JOB_SERVICE', 0, 'zingg-async-job-container'); +``` +`zingg-async-job-container` refers to the container name defined in the job specification. + +**Stop the compute pool** + +Once the job completes, stop the compute pool to release resources. + +```sql +ALTER COMPUTE POOL CONTAINER_ZINGG_POOL STOP ALL; +``` + {% endtab %} -{% endtabs %} +{% endtabs %} \ No newline at end of file From 7536535a370bd86085e31dd0c1b7da092727bfd0 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Sun, 16 Aug 2026 17:24:30 +0000 Subject: [PATCH 042/144] GITBOOK-330: No subject --- .../zingg-concepts/how-zingg-learns/README.md | 4 ++-- .../zingg-models/blocking-model.md | 20 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/zingg-concepts/how-zingg-learns/README.md b/docs/zingg-concepts/how-zingg-learns/README.md index 750bb33b6..aea93a4c4 100644 --- a/docs/zingg-concepts/how-zingg-learns/README.md +++ b/docs/zingg-concepts/how-zingg-learns/README.md @@ -21,14 +21,14 @@ Zingg starts by scanning your dataset and selecting the most informative candida You label those pairs typically 30 to 50, as Match, No Match, or Uncertain. From those labels, Zingg builds two models: a blocking model that cuts down the comparison space to a tiny fraction of all possible pairs, and a similarity model that scores each remaining candidate pair with graded confidence. -The result is a model that understands your data. Not a generic algorithm applied to it. +Zingg models understand your data, and are not a generic algorithm applied to it. #### The problem Zingg is built to solve and why it is harder than it looks Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem.\ And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. -
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.

This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.

Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always break

Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.

A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.

Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.

The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.

The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.

Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.

Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely.

Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

The data drift problem - why a model trained once decays

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.

Rule-based systems have no mechanism to detect or adapt to these changes. You rewrite the rules manually when performance degrades, which means that someone has to notice the degradation first.

Zingg Enterprise's incremental flow detects when new records do not fit existing clusters cleanly and surfaces them for review. The compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live.

The incremental new data problem

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.

First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.

Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

+
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.

This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.

Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always break

Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.

A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.

Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.

The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.

The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.

Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.

Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely.

Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

The data drift problem - why a model trained once decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.

Rule-based systems have no mechanism to detect or adapt to these changes. You rewrite the rules manually when performance degrades, which means that someone has to notice the degradation first.

Zingg Enterprise's incremental flow detects when new records do not fit existing clusters cleanly and surfaces them for review. The compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live.

The incremental new data problem

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.

First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.

Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

### The active learning loop diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md index a99eae095..c0cf40432 100644 --- a/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md @@ -1,11 +1,15 @@ ---- -description: >- - How to inspect the blocking model, understand its coverage, and fix missed - matches caused by blocking. ---- - # Blocking Model +When your data contains reliable unique identifiers, a national ID, an email, or a combination like first name plus date of birth + + + +TODO - move to verify blocking + +How to inspect the blocking model, understand its coverage, and fix missed matches caused by blocking. + + + The blocking model is the first filter in every Zingg run. It decides which record pairs the similarity model ever sees. If it places two matching records in different buckets, those records will never be compared, and the match will be missed regardless of how well everything else is configured. If your results are missing matches you expect to see, start here. @@ -75,12 +79,12 @@ influence matching. 1. Add more labeled training pairs of the type being missed. The blocking model learns from the same training data as the similarity model. Focus on pairs that represent the variation patterns missing from your current training set. 2. Review your field match types. Fields marked `DONT_US`E are excluded from blocking as well as similarity. If a field has a strong identity signal - a consistent identifier that appears across matching records - consider changing it from `DONT_USE` to `FUZZY`. 3. Check whether missed pairs share a common characteristic. If all missed pairs have empty values in a key field, empty fields cannot contribute to blocking. Either remove that field from your blocking config or improve data completeness upstream. -4. If standard blocking consistently misses a specific pattern in your data, consider custom blocking functions. +4. If standard blocking consistently misses a specific pattern in your data, consider custom blocking functions. {% hint style="success" icon="right-long" %} **Read more**: * [Label Training Pairs](../../../running-zingg/label-training-pairs.md) - how to add more training data * [Configure Zingg](../../../running-zingg/configure-zingg.md) - changing field match types -* [Custom Blocking and Similarity](../../../tuning/custom-blocking-and-similarity.md) Functions +* [Custom Blocking and Similarity](../../../tuning/custom-blocking-and-similarity.md) Functions {% endhint %} From 8989a89ea54b557e1827b36b9c3e24509f498bd1 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Sun, 16 Aug 2026 17:27:39 +0000 Subject: [PATCH 043/144] GITBOOK-331: No subject --- .../zingg-concepts/how-zingg-learns/README.md | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/zingg-concepts/how-zingg-learns/README.md b/docs/zingg-concepts/how-zingg-learns/README.md index aea93a4c4..6b70571c4 100644 --- a/docs/zingg-concepts/how-zingg-learns/README.md +++ b/docs/zingg-concepts/how-zingg-learns/README.md @@ -1,7 +1,7 @@ --- description: >- - How Zingg builds a matching model from your data and why it handles scale, - variation, and uncertainty in ways that rules-based approaches cannot. + How Zingg builds ML models from your data and why it handles scale, variation, + and uncertainty in ways that other approaches cannot. --- # How Zingg Learns @@ -13,9 +13,14 @@ description: >- * For a deep dive on the two models Zingg builds internally - [Zingg Models](zingg-models/) {% endhint %} -Most entity resolution ML tools give you two options: write rules by hand or load a pre-trained model and hope it generalizes to your data. +Zingg learns specific ML models from your data, calibrated to your specific field distributions, your specific variations, and your specific understanding of what a match means. -Zingg does neither. It learns a model from your data, calibrated to your specific field distributions, your specific variations, and your specific understanding of what a match means. +#### The problems Zingg is built to solve and why they are harder than it looks + +Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem.\ +And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. + +
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.

This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.

Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always break

Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.

A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.

Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.

The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.

The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.

Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.

Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely.

Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

The data drift problem - why a model trained once decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.

Rule-based systems have no mechanism to detect or adapt to these changes. You rewrite the rules manually when performance degrades, which means that someone has to notice the degradation first.

Zingg Enterprise's incremental flow detects when new records do not fit existing clusters cleanly and surfaces them for review. The compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live.

The incremental new data problem

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.

First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.

Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Zingg starts by scanning your dataset and selecting the most informative candidate pairs, edge cases, near misses, and ambiguous records where human judgment matters most. It does not ask you to label random samples. It is highly selective about which pairs it surfaces, choosing the ones that will teach it the most with the least labeling effort. @@ -23,13 +28,6 @@ You label those pairs typically 30 to 50, as Match, No Match, or Uncertain. From Zingg models understand your data, and are not a generic algorithm applied to it. -#### The problem Zingg is built to solve and why it is harder than it looks - -Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem.\ -And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. - -
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.

This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.

Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always break

Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.

A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.

Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.

The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.

The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.

Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.

Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely.

Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

The data drift problem - why a model trained once decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.

Rule-based systems have no mechanism to detect or adapt to these changes. You rewrite the rules manually when performance degrades, which means that someone has to notice the degradation first.

Zingg Enterprise's incremental flow detects when new records do not fit existing clusters cleanly and surfaces them for review. The compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live.

The incremental new data problem

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.

First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.

Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

- ### The active learning loop Zingg does not require thousands of pre-labeled training examples. It uses an active learning approach: it finds the record pairs where it is most uncertain, presents them to you, and learns from your labels. From c2b71ee206c9f029f67ebb1af5e7948a31e1296e Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Sun, 16 Aug 2026 17:28:57 +0000 Subject: [PATCH 044/144] GITBOOK-332: No subject --- .../entity-resolution/rule-based-entity-resolution.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/zingg-concepts/entity-resolution/rule-based-entity-resolution.md b/docs/zingg-concepts/entity-resolution/rule-based-entity-resolution.md index e3edcce70..5d1b38684 100644 --- a/docs/zingg-concepts/entity-resolution/rule-based-entity-resolution.md +++ b/docs/zingg-concepts/entity-resolution/rule-based-entity-resolution.md @@ -10,8 +10,8 @@ Rule-based entity resolution fails for three reasons: 2. **Rules cannot score confidence**. A rule fires, or it does not. It cannot tell you that two records are probably the same entity or that a cluster has a weak link worth human review. Entity resolution at production scale requires a graded confidence signal; not a binary match/no-match. 3. **Rules do not scale to the comparison space**. At one million records, the naive approach requires evaluating 500 billion record pairs. At ten million, it is 50 trillion. A rule engine applied to every pair is computationally impossible. -Zingg's ML model solves all three: +Zingg's ML models solve all three: -* It learns variation from your data - roughly 30 - 50 labeled examples are enough to build a model that generalizes to patterns it has not seen before. +* Zingg learns variation from your data - roughly 30 - 50 labeled examples are enough to build a model that generalizes to patterns it has not seen before. * It produces a graded confidence score (`Z_MINSCORE` and `Z_MAXSCORE`) per cluster, so you can route high-confidence matches to automated processing and low-confidence matches to human review. * It learns a blocking model to cut down the comparison space from billions of pairs to a tiny fraction without losing recall, so the similarity model only checks candidate pairs that could plausibly match. From 488c8683db35e4032ed40ba7bf33836b84b11fcf Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Sun, 16 Aug 2026 17:33:06 +0000 Subject: [PATCH 045/144] GITBOOK-333: No subject --- docs/SUMMARY.md | 2 +- .../platform-guide-for-azure-databricks.md | 4 ++-- docs/platform-guides/platform-guide-for-gcp-dataproc.md | 2 +- .../platform-guide-for-microsoft-fabric.md | 2 +- docs/zingg-concepts/identity-graph.md | 5 +---- .../zingg-entity-resolution-platform.md | 8 ++++---- 6 files changed, 10 insertions(+), 13 deletions(-) rename docs/zingg-concepts/{entity-resolution => }/zingg-entity-resolution-platform.md (58%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 7759b1b63..8fd72c62e 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -5,9 +5,9 @@ * [What is Zingg](README.md) * [Entity Resolution](zingg-concepts/entity-resolution/README.md) * [Rule based entity resolution](zingg-concepts/entity-resolution/rule-based-entity-resolution.md) - * [Zingg Entity Resolution Platform](zingg-concepts/entity-resolution/zingg-entity-resolution-platform.md) * [Deterministic vs Probabilistic Matching](zingg-concepts/entity-resolution/deterministic-vs-probabilistic-matching.md) * [Deduplication, linking and resolving](zingg-concepts/entity-resolution/deduplication-linking-and-resolving.md) +* [Zingg Entity Resolution Platform](zingg-concepts/zingg-entity-resolution-platform.md) * [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) * [How Zingg Learns](zingg-concepts/how-zingg-learns/README.md) diff --git a/docs/platform-guides/platform-guide-for-azure-databricks.md b/docs/platform-guides/platform-guide-for-azure-databricks.md index b9c5503ae..095187ce6 100644 --- a/docs/platform-guides/platform-guide-for-azure-databricks.md +++ b/docs/platform-guides/platform-guide-for-azure-databricks.md @@ -305,7 +305,7 @@ DOCS_DIR = zinggDir + "/" + modelId + "/docs/" displayHTML(open(DOCS_DIR + "model.html", 'r').read()) ``` -_**IMAGE TO BE ADDED —**** ****`generateDocs`**** ****HTML output rendered inside a Databricks notebook showing labeled pair examples. Tanwi to check with team for screenshot from a live notebook run. Even a small portion of the rendered HTML is sufficient — it tells the reader what to expect before they run it. Place: below the**** ****`displayHTML`****\*\*\*\*\*\*\*\* \*\*\*\*line.**_ +_**IMAGE TO BE ADDED —**** ****`generateDocs`**** ****HTML output rendered inside a Databricks notebook showing labeled pair examples. Tanwi to check with team for screenshot from a live notebook run. Even a small portion of the rendered HTML is sufficient — it tells the reader what to expect before they run it. Place: below the**** ****`displayHTML`****\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*line.**_ {% endtab %} {% tab title="Enterprise" %} @@ -628,7 +628,7 @@ data_html = "\n".join(r.value for r in data_doc.collect()) displayHTML(data_html) ``` -_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\* \*\*\*\*output rendered inside a Databricks notebook showing labeled pair examples in HTML. Tanwi to check with team for screenshot from a live notebook run. Can reuse the OS version if the output looks the same.**_ +_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\* \*\*\*\*output rendered inside a Databricks notebook showing labeled pair examples in HTML. Tanwi to check with team for screenshot from a live notebook run. Can reuse the OS version if the output looks the same.**_ ### Notebook 05: Train and match diff --git a/docs/platform-guides/platform-guide-for-gcp-dataproc.md b/docs/platform-guides/platform-guide-for-gcp-dataproc.md index 9ab8951e7..2b14e5584 100644 --- a/docs/platform-guides/platform-guide-for-gcp-dataproc.md +++ b/docs/platform-guides/platform-guide-for-gcp-dataproc.md @@ -411,7 +411,7 @@ with open(DOCS_DIR + "data.html", 'r') as f: display(HTML(f.read())) ``` -_**IMAGE TO BE ADDED—**** ****`generateDocs`****\*\*\*\* \*\*\*\*HTML output rendered inside JupyterLab on Dataproc showing labeled pair examples. Source: not in the GCP docx. Tanwi to check with team for screenshot from a live notebook run.**_ +_**IMAGE TO BE ADDED—**** ****`generateDocs`****\*\*\*\*\*\*\*\* \*\*\*\*HTML output rendered inside JupyterLab on Dataproc showing labeled pair examples. Source: not in the GCP docx. Tanwi to check with team for screenshot from a live notebook run.**_ {% hint style="success" icon="right-long" %} `generateDocs` is optional. Skip it if you have 30–40 matches and 30–40 non-matches and are confident in your labeling quality. diff --git a/docs/platform-guides/platform-guide-for-microsoft-fabric.md b/docs/platform-guides/platform-guide-for-microsoft-fabric.md index f2d6a93cf..714b5b589 100644 --- a/docs/platform-guides/platform-guide-for-microsoft-fabric.md +++ b/docs/platform-guides/platform-guide-for-microsoft-fabric.md @@ -414,7 +414,7 @@ displayHTML(open(DOCS_DIR + "model.html", 'r').read()) displayHTML(open(DOCS_DIR + "data.html", 'r').read()) ``` -_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\* \*\*\*\*HTML output rendered inside a Fabric notebook showing labeled pair examples in a table. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ +_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\* \*\*\*\*HTML output rendered inside a Fabric notebook showing labeled pair examples in a table. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ ### Notebook 04: Train and match diff --git a/docs/zingg-concepts/identity-graph.md b/docs/zingg-concepts/identity-graph.md index b94219944..d6510f17c 100644 --- a/docs/zingg-concepts/identity-graph.md +++ b/docs/zingg-concepts/identity-graph.md @@ -17,8 +17,6 @@ Zingg's identity graph is this structure. What differs between Community and Ent * **Enterprise:** `Zingg ID`, stable, globally unique, persistent across incremental updates. {% endhint %} -
- ### Community - a complete identity graph, rebuilt fresh every run Every Zingg Community run produces a complete, queryable identity graph. Every record in your dataset is assigned a `Z Cluster`. All records sharing the same `Z Cluster` represent the same real-world entity. @@ -47,7 +45,6 @@ Enterprise gives you the same graph with stable `Zingg IDs` , so the graph can g * [Z Cluster vs Zingg ID](z-cluster-and-zingg-id.md) * [Zingg Models](how-zingg-learns/zingg-models/) -* [Run incremental matching](../running-zingg/run-incremental-matching.md) +* [Run incremental matching](../running-zingg/run-incremental-matching.md) * [Community Vs Enterprise](community-vs-enterprise/) {% endhint %} - diff --git a/docs/zingg-concepts/entity-resolution/zingg-entity-resolution-platform.md b/docs/zingg-concepts/zingg-entity-resolution-platform.md similarity index 58% rename from docs/zingg-concepts/entity-resolution/zingg-entity-resolution-platform.md rename to docs/zingg-concepts/zingg-entity-resolution-platform.md index 016af6154..127c3cbb3 100644 --- a/docs/zingg-concepts/entity-resolution/zingg-entity-resolution-platform.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform.md @@ -4,7 +4,7 @@ Zingg is an ML-powered entity resolution engine built to run where your data alr Three capabilities work together to handle the full problem: -
Warehouse-native executionProbabilistic + deterministic matchingPersistent identity graph
Zingg runs inside Databricks, Microsoft
Fabric, Snowflake, GCP Dataproc, AWS Glue, and AWS EMR. Your data never
leaves your environment. No ETL pipelines. No external APIs. There is no separate infrastructure to operate. The same model that runs on 100,000 records scales to hundreds of millions using your existing Spark or Snowflake compute, without any architectural changes.

Probabilistic matching is Zingg's default and available in the Community Edition. The ML model learns from your labeled pairs; 30 to 50 examples are enough to build a model calibrated
to your specific data and scores every candidate pair on multiple field-level features. It handles
typos, abbreviations, missing values, and format variations automatically.

Deterministic matching (Enterprise) adds hard rules for trusted identifiers.
When two records share the same SSN, tax ID, or email, Zingg treats them
as the same entity without consulting the ML model. Both approaches run in a single flow.

Deterministic vs Probabilistic
Matching

Every resolved entity receives a Zingg ID, a globally unique, persistent GUID assigned in Enterprise that remains stable
across runs, incremental updates, and model changes.

Community produces a Z Cluster
which is unique. Matching records share the same Z_Cluster that is reassigned each run.


Enterprise produces a Zingg ID you can store in downstream systems
with confidence.

The identity graph grows incrementally. New records are matched to existing clusters without rerunning on your full dataset.

Identity Graph
Z Cluster and Zingg ID

+
Warehouse-native executionProbabilistic + deterministic matchingPersistent identity graph
Zingg runs inside Databricks, Microsoft
Fabric, Snowflake, GCP Dataproc, AWS Glue, and AWS EMR. Your data never
leaves your environment. No ETL pipelines. No external APIs. There is no separate infrastructure to operate. The same model that runs on 100,000 records scales to hundreds of millions using your existing Spark or Snowflake compute, without any architectural changes.

Probabilistic matching is Zingg's default and available in the Community Edition. The ML model learns from your labeled pairs; 30 to 50 examples are enough to build a model calibrated
to your specific data and scores every candidate pair on multiple field-level features. It handles
typos, abbreviations, missing values, and format variations automatically.

Deterministic matching (Enterprise) adds hard rules for trusted identifiers.
When two records share the same SSN, tax ID, or email, Zingg treats them
as the same entity without consulting the ML model. Both approaches run in a single flow.

Deterministic vs Probabilistic
Matching

Every resolved entity receives a Zingg ID, a globally unique, persistent GUID assigned in Enterprise that remains stable
across runs, incremental updates, and model changes.

Community produces a Z Cluster
which is unique. Matching records share the same Z_Cluster that is reassigned each run.


Enterprise produces a Zingg ID you can store in downstream systems
with confidence.

The identity graph grows incrementally. New records are matched to existing clusters without rerunning on your full dataset.

Identity Graph
Z Cluster and Zingg ID

This is the combination that makes enterprise-scale entity resolution computationally feasible and practically maintainable. @@ -13,9 +13,9 @@ This is the combination that makes enterprise-scale entity resolution computatio {% hint style="success" icon="right-long" %} **Read more**: -* [Deterministic vs Probabilistic Matching](deterministic-vs-probabilistic-matching.md) -* [How Zingg Learns](../how-zingg-learns/) -* [Zingg Models](../how-zingg-learns/zingg-models/) (blocking + similarity) +* [Deterministic vs Probabilistic Matching](entity-resolution/deterministic-vs-probabilistic-matching.md) +* [How Zingg Learns](how-zingg-learns/) +* [Zingg Models](how-zingg-learns/zingg-models/) (blocking + similarity) {% endhint %} [^1]: From f74cdb63b56ac08c0fe4f09e8127c6a7e391c416 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Sun, 16 Aug 2026 17:37:09 +0000 Subject: [PATCH 046/144] GITBOOK-334: No subject --- docs/SUMMARY.md | 6 +++--- docs/reference/configuration-schema.md | 2 +- .../community-vs-enterprise/README.md | 2 +- docs/zingg-concepts/concept-glossary.md | 14 +++++++------- .../how-zingg-learns/match-types/exact-match.md | 7 +++---- .../README.md} | 8 ++++---- .../deduplication-linking-and-resolving.md | 0 .../deterministic-vs-probabilistic-matching.md | 0 .../enterprise-zinggec-python-api.md | 2 +- 9 files changed, 20 insertions(+), 21 deletions(-) rename docs/zingg-concepts/{zingg-entity-resolution-platform.md => zingg-entity-resolution-platform/README.md} (58%) rename docs/zingg-concepts/{entity-resolution => zingg-entity-resolution-platform}/deduplication-linking-and-resolving.md (100%) rename docs/zingg-concepts/{entity-resolution => zingg-entity-resolution-platform}/deterministic-vs-probabilistic-matching.md (100%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 8fd72c62e..f827fc38b 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -5,9 +5,9 @@ * [What is Zingg](README.md) * [Entity Resolution](zingg-concepts/entity-resolution/README.md) * [Rule based entity resolution](zingg-concepts/entity-resolution/rule-based-entity-resolution.md) - * [Deterministic vs Probabilistic Matching](zingg-concepts/entity-resolution/deterministic-vs-probabilistic-matching.md) - * [Deduplication, linking and resolving](zingg-concepts/entity-resolution/deduplication-linking-and-resolving.md) -* [Zingg Entity Resolution Platform](zingg-concepts/zingg-entity-resolution-platform.md) +* [Zingg Entity Resolution Platform](zingg-concepts/zingg-entity-resolution-platform/README.md) + * [Deterministic vs Probabilistic Matching](zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) + * [Deduplication, linking and resolving](zingg-concepts/zingg-entity-resolution-platform/deduplication-linking-and-resolving.md) * [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) * [How Zingg Learns](zingg-concepts/how-zingg-learns/README.md) diff --git a/docs/reference/configuration-schema.md b/docs/reference/configuration-schema.md index ed45ff07a..04d6fde8f 100644 --- a/docs/reference/configuration-schema.md +++ b/docs/reference/configuration-schema.md @@ -14,7 +14,7 @@ This page is the reference. Every Zingg configuration parameter, JSON key and Py ### Top-level parameters -
ParameterTypeEditionDescription
modelIdstringAll editionsUnique identifier for this model. Used as folder name under zinggDir. Use the same modelId across all phases for a given run.
zinggDirstringAll editionsRoot directory where Zingg writes model files and training data. Can be a local path, DBFS path (dbfs:/), GCS path (gs://), S3 path (s3a://), or OneLake path (abfss://).
numPartitionsintegerAll editionsNumber of Spark partitions. Controls how data is distributed across cluster nodes. Rule of thumb: set to approximately 20–30× the number of worker vCPUs. Start with 4–8 for a standard development cluster. Increase proportionally for larger datasets.
labelDataSampleSizefloat (0.0001–0.1)All editionsFraction of the dataset scanned when running findTrainingData. Valid range: 0.0001 to 0.1. For 100k records use 0.1–0.5. For 1M+ records use 0.01–0.05. Reduce to 0.05 or lower if findTrainingData is slow on large datasets.
stopWordsCutofffloat (0–1)All editions, optionalUsed with the recommend phase. Fraction of high-frequency words to extract as stopword candidates. Default is 0.1 (10%).
collectMetricsbooleanAll editionsControls telemetry collection. Default true. Set to false to disable. When enabled, Zingg captures runtime metrics (record count, field count, running phase, execution time). No input data or user data is ever captured. See Security and Privacy for full details.
passthroughExprstringEnterprise only, optionalSQL expression defining records to exclude from matching. Records matching this expression are carried through to output with their own Zingg ID but do not influence cluster formation. Example: "fname = 'matilda'".
deterministicMatchingarrayEnterprise only, optionalDeterministic match conditions, where exact field matches always result in a match regardless of the probabilistic score. See Deterministic vs Probabilistic Matching for full configuration.
setBlockingModelstringEnterprise only, optionalBlocking strategy. Valid values: "DEFAULT", "WIDER". If not set, the model uses DEFAULT.
+
ParameterTypeEditionDescription
modelIdstringAll editionsUnique identifier for this model. Used as folder name under zinggDir. Use the same modelId across all phases for a given run.
zinggDirstringAll editionsRoot directory where Zingg writes model files and training data. Can be a local path, DBFS path (dbfs:/), GCS path (gs://), S3 path (s3a://), or OneLake path (abfss://).
numPartitionsintegerAll editionsNumber of Spark partitions. Controls how data is distributed across cluster nodes. Rule of thumb: set to approximately 20–30× the number of worker vCPUs. Start with 4–8 for a standard development cluster. Increase proportionally for larger datasets.
labelDataSampleSizefloat (0.0001–0.1)All editionsFraction of the dataset scanned when running findTrainingData. Valid range: 0.0001 to 0.1. For 100k records use 0.1–0.5. For 1M+ records use 0.01–0.05. Reduce to 0.05 or lower if findTrainingData is slow on large datasets.
stopWordsCutofffloat (0–1)All editions, optionalUsed with the recommend phase. Fraction of high-frequency words to extract as stopword candidates. Default is 0.1 (10%).
collectMetricsbooleanAll editionsControls telemetry collection. Default true. Set to false to disable. When enabled, Zingg captures runtime metrics (record count, field count, running phase, execution time). No input data or user data is ever captured. See Security and Privacy for full details.
passthroughExprstringEnterprise only, optionalSQL expression defining records to exclude from matching. Records matching this expression are carried through to output with their own Zingg ID but do not influence cluster formation. Example: "fname = 'matilda'".
deterministicMatchingarrayEnterprise only, optionalDeterministic match conditions, where exact field matches always result in a match regardless of the probabilistic score. See Deterministic vs Probabilistic Matching for full configuration.
setBlockingModelstringEnterprise only, optionalBlocking strategy. Valid values: "DEFAULT", "WIDER". If not set, the model uses DEFAULT.
### `fieldDefinition` diff --git a/docs/zingg-concepts/community-vs-enterprise/README.md b/docs/zingg-concepts/community-vs-enterprise/README.md index da928ec8f..de839ed35 100644 --- a/docs/zingg-concepts/community-vs-enterprise/README.md +++ b/docs/zingg-concepts/community-vs-enterprise/README.md @@ -35,7 +35,7 @@ The step from Community to Enterprise Lite is a step from evaluation to producti Enterprise Lite also gives you: * [**Incremental matching**](../../running-zingg/run-incremental-matching.md) **-** update the identity graph as new records arrive, without re-running on your entire dataset -* [**Deterministic matching**](../entity-resolution/deterministic-vs-probabilistic-matching.md) **-** combine hard rules for known trusted identifiers (SSN, tax ID, email) with probabilistic ML in a single flow +* [**Deterministic matching**](../zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) **-** combine hard rules for known trusted identifiers (SSN, tax ID, email) with probabilistic ML in a single flow * [**Pass Through**](../pass-through.md) **-** records that are incomplete or unsuitable for matching still appear in your output and receive a Zingg ID for governance and compliance * [**Production-grade notebooks**](../../platform-guides/platform-guide-for-azure-databricks.md) **-** a 7-notebook sequence, one per workflow phase, built for repeatable production runs diff --git a/docs/zingg-concepts/concept-glossary.md b/docs/zingg-concepts/concept-glossary.md index 46cb26bac..dfae15300 100644 --- a/docs/zingg-concepts/concept-glossary.md +++ b/docs/zingg-concepts/concept-glossary.md @@ -47,11 +47,11 @@ typically enough to train a high-accuracy model on datasets of 100,000+ records. Zingg's default matching mode. The model learns similarity weights for each field from your labeled pairs and assigns a match score to every candidate pair. Records above the threshold are grouped into a cluster. The threshold is automatically chosen to balance accuracy and recall. Zingg optimizes it so you do not need to tune a cut-off manually. -Probabilistic matching handles variations in names, addresses, and other messy real-world data that exact rules cannot account for. \ -&#xNAN;_Available in all editions_ +Probabilistic matching handles variations in names, addresses, and other messy real-world data that exact rules cannot account for.\ +\&#xNAN;_Available in all editions_ {% hint style="success" icon="right-long" %} -**Read more:** [Deterministic vs probabilistic matching](entity-resolution/deterministic-vs-probabilistic-matching.md) +**Read more:** [Deterministic vs probabilistic matching](zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) {% endhint %} @@ -62,11 +62,11 @@ Probabilistic matching handles variations in names, addresses, and other messy r User-defined hard rules that force a match regardless of the probabilistic score. If the fields you specify all match exactly; for example, tax ID, date of birth, and registered address Zingg treats those records as the same entity without consulting the ML model. -Both approaches run in a single flow; deterministic rules are evaluated first, and probabilistic matching handles everything else. \ -&#xNAN;_**Enterprise only**_ +Both approaches run in a single flow; deterministic rules are evaluated first, and probabilistic matching handles everything else.\ +\&#xNAN;_**Enterprise only**_ {% hint style="success" icon="right-long" %} -**Read more:** [Deterministic vs probabilistic matching](entity-resolution/deterministic-vs-probabilistic-matching.md) +**Read more:** [Deterministic vs probabilistic matching](zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) {% endhint %} @@ -287,7 +287,7 @@ Reports what percentage of your known matching pairs are being blocked together
-stopwords recommend +stopwords recommend Analyses a specified column and returns a list of high-frequency words that should be treated as stopwords. Optional phase used before training when address or company name fields contain repeated noise words like 'St', 'Ave', 'Inc', 'Ltd'. diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md index fa4cd47f8..654b8924a 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md @@ -27,7 +27,7 @@ These fields are reliable unique identifiers. An exact match on SSN means the tw Always use `EXACT` on trusted identifiers. In Zingg Enterprise, also add them as deterministic matching conditions, an exact match on a trusted identifier produces a guaranteed match with score 1 before the probabilistic model runs. -→ [Deterministic vs Probabilistic Matching](../../entity-resolution/deterministic-vs-probabilistic-matching.md) +→ [Deterministic vs Probabilistic Matching](../../zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md)
@@ -102,7 +102,7 @@ dob = EFieldDefinition("dob", "string", MatchType.EXACT) {% tab title="JSON" %} {% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). +The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). {% endhint %} ```json @@ -123,7 +123,6 @@ The JSON `fieldDefinition` block is identical for Community and Enterprise. Only ] } ``` - {% endtab %} {% endtabs %} @@ -132,7 +131,7 @@ The JSON `fieldDefinition` block is identical for Community and Enterprise. Only * `FUZZY` - use when variation is expected * `PINCODE` - EXACT-like for postal codes with format normalisation built in -* `DONT_USE` - exclude from matching entirely +* `DONT_USE` - exclude from matching entirely **Read more**: [Match Types](./) {% endhint %} diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform.md b/docs/zingg-concepts/zingg-entity-resolution-platform/README.md similarity index 58% rename from docs/zingg-concepts/zingg-entity-resolution-platform.md rename to docs/zingg-concepts/zingg-entity-resolution-platform/README.md index 127c3cbb3..016af6154 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/README.md @@ -4,7 +4,7 @@ Zingg is an ML-powered entity resolution engine built to run where your data alr Three capabilities work together to handle the full problem: -
Warehouse-native executionProbabilistic + deterministic matchingPersistent identity graph
Zingg runs inside Databricks, Microsoft
Fabric, Snowflake, GCP Dataproc, AWS Glue, and AWS EMR. Your data never
leaves your environment. No ETL pipelines. No external APIs. There is no separate infrastructure to operate. The same model that runs on 100,000 records scales to hundreds of millions using your existing Spark or Snowflake compute, without any architectural changes.

Probabilistic matching is Zingg's default and available in the Community Edition. The ML model learns from your labeled pairs; 30 to 50 examples are enough to build a model calibrated
to your specific data and scores every candidate pair on multiple field-level features. It handles
typos, abbreviations, missing values, and format variations automatically.

Deterministic matching (Enterprise) adds hard rules for trusted identifiers.
When two records share the same SSN, tax ID, or email, Zingg treats them
as the same entity without consulting the ML model. Both approaches run in a single flow.

Deterministic vs Probabilistic
Matching

Every resolved entity receives a Zingg ID, a globally unique, persistent GUID assigned in Enterprise that remains stable
across runs, incremental updates, and model changes.

Community produces a Z Cluster
which is unique. Matching records share the same Z_Cluster that is reassigned each run.


Enterprise produces a Zingg ID you can store in downstream systems
with confidence.

The identity graph grows incrementally. New records are matched to existing clusters without rerunning on your full dataset.

Identity Graph
Z Cluster and Zingg ID

+
Warehouse-native executionProbabilistic + deterministic matchingPersistent identity graph
Zingg runs inside Databricks, Microsoft
Fabric, Snowflake, GCP Dataproc, AWS Glue, and AWS EMR. Your data never
leaves your environment. No ETL pipelines. No external APIs. There is no separate infrastructure to operate. The same model that runs on 100,000 records scales to hundreds of millions using your existing Spark or Snowflake compute, without any architectural changes.

Probabilistic matching is Zingg's default and available in the Community Edition. The ML model learns from your labeled pairs; 30 to 50 examples are enough to build a model calibrated
to your specific data and scores every candidate pair on multiple field-level features. It handles
typos, abbreviations, missing values, and format variations automatically.

Deterministic matching (Enterprise) adds hard rules for trusted identifiers.
When two records share the same SSN, tax ID, or email, Zingg treats them
as the same entity without consulting the ML model. Both approaches run in a single flow.

Deterministic vs Probabilistic
Matching

Every resolved entity receives a Zingg ID, a globally unique, persistent GUID assigned in Enterprise that remains stable
across runs, incremental updates, and model changes.

Community produces a Z Cluster
which is unique. Matching records share the same Z_Cluster that is reassigned each run.


Enterprise produces a Zingg ID you can store in downstream systems
with confidence.

The identity graph grows incrementally. New records are matched to existing clusters without rerunning on your full dataset.

Identity Graph
Z Cluster and Zingg ID

This is the combination that makes enterprise-scale entity resolution computationally feasible and practically maintainable. @@ -13,9 +13,9 @@ This is the combination that makes enterprise-scale entity resolution computatio {% hint style="success" icon="right-long" %} **Read more**: -* [Deterministic vs Probabilistic Matching](entity-resolution/deterministic-vs-probabilistic-matching.md) -* [How Zingg Learns](how-zingg-learns/) -* [Zingg Models](how-zingg-learns/zingg-models/) (blocking + similarity) +* [Deterministic vs Probabilistic Matching](deterministic-vs-probabilistic-matching.md) +* [How Zingg Learns](../how-zingg-learns/) +* [Zingg Models](../how-zingg-learns/zingg-models/) (blocking + similarity) {% endhint %} [^1]: diff --git a/docs/zingg-concepts/entity-resolution/deduplication-linking-and-resolving.md b/docs/zingg-concepts/zingg-entity-resolution-platform/deduplication-linking-and-resolving.md similarity index 100% rename from docs/zingg-concepts/entity-resolution/deduplication-linking-and-resolving.md rename to docs/zingg-concepts/zingg-entity-resolution-platform/deduplication-linking-and-resolving.md diff --git a/docs/zingg-concepts/entity-resolution/deterministic-vs-probabilistic-matching.md b/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md similarity index 100% rename from docs/zingg-concepts/entity-resolution/deterministic-vs-probabilistic-matching.md rename to docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md diff --git a/docs/zingg-python-api/enterprise-zinggec-python-api.md b/docs/zingg-python-api/enterprise-zinggec-python-api.md index d4643623d..3bea2cf81 100644 --- a/docs/zingg-python-api/enterprise-zinggec-python-api.md +++ b/docs/zingg-python-api/enterprise-zinggec-python-api.md @@ -150,7 +150,7 @@ args.setDeterministicMatchingCondition(dm1, dm2, dm3) ``` {% hint style="success" icon="right-long" %} -**Read more**: For full deterministic matching concepts and configuration → [Deterministic vs Probabilistic Matching](../zingg-concepts/entity-resolution/deterministic-vs-probabilistic-matching.md) +**Read more**: For full deterministic matching concepts and configuration → [Deterministic vs Probabilistic Matching](../zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) {% endhint %} ### Output statistics From 62805ad221a60650db9caf0c90b0fa7e6c2da19c Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Sun, 16 Aug 2026 18:01:06 +0000 Subject: [PATCH 047/144] GITBOOK-335: No subject --- .../community-vs-enterprise/README.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/docs/zingg-concepts/community-vs-enterprise/README.md b/docs/zingg-concepts/community-vs-enterprise/README.md index de839ed35..eaaabb497 100644 --- a/docs/zingg-concepts/community-vs-enterprise/README.md +++ b/docs/zingg-concepts/community-vs-enterprise/README.md @@ -6,23 +6,19 @@ description: >- # Community vs Enterprise -Every Zingg edition is built on the same foundation: probabilistic matching, active learning, and warehouse-native execution. Enterprise takes that further delivering faster, more accurate entity resolution with deterministic rules, explainability, and full model lifecycle control +Every Zingg edition is built on the same foundation: probabilistic matching, active learning, and warehouse-native execution. What changes is the identity infrastructure, operational control, and advanced matching capability you obtain on top of it. -What changes is the identity infrastructure, operational control, and advanced matching capability you obtain on top of it. - -Community is the right place to start. Enterprise is where you go when identity resolution moves into production and needs to stay accurate, auditable, and stable over time. - -
CommunityEnterprise LiteEnterprise
Who it is forTeams that are evaluating Zingg, running proofs of concept, or operating on self-managed Spark infrastructure.Teams that are moving entity resolution into production and need persistent entity IDs and incremental updates.Teams who need full lifecycle control: seamless model upgrades, governance, explainability, and dictionary-based matching for complex data.
Identity graphZ Cluster is a unique cluster identifier assigned per run.Persistent Zingg ID (GUID). Stable across runs and safe to reference in CRMs, warehouses, and pipelines.Persistent Zingg ID plus seamless ID reassignment when you upgrade your model. Downstream systems stay intact across changes.
Matching capabilityProbabilistic matching trained on your labeled pairs. Handles typos, abbreviations, and variations across any field type.Probabilistic + deterministic matching in a single flow. Hard rules for trusted identifiers like SSN, tax ID, email. Pass Through for records that should appear in output but not influence matching.Everything in Lite, plus dictionary-based matching (nicknames, aliases, company name variants). Standardize fields before and after matching. Match statistics and explainability for audit and governance.
Model operationsTrain once and match as data arrives.Incremental runs update the identity graph as new records arrive without retraining. Verify blocking before committing to training.Everything in Lite, plus compare two trained models side by side before deploying. Upgrade models without disrupting Zingg IDs. Re-assign IDs from an old model to a new one.
PlatformAny Spark environment - Databricks, Fabric, EMR, GCP Dataproc, and local Spark.All Community platforms, plus native Snowflake run (no Spark cluster required), Unity Catalog, and OneLake integration.All platforms in Lite.
+
CommunityEnterprise LiteEnterprise
Who it is forTeams that are evaluating Zingg, running proofs of concept, or do not need a persistent identity graph with continously updated data.Teams that want to run entity resolution into production and need persistent entity IDs, deterministic matching and incremental updates.Teams who need full lifecycle control: seamless model upgrades, governance, explainability, and dictionary-based matching for complex data.
Identity graphZ Cluster is a unique cluster identifier assigned per run.Persistent Zingg ID (GUID). Stable across runs and safe to reference in CRMs, warehouses, and pipelines.Persistent Zingg ID plus seamless ID reassignment when you upgrade your model. Downstream systems stay intact across changes.
Matching capabilityProbabilistic matching trained on your labeled pairs. Handles typos, abbreviations, and variations across any field type.Probabilistic + deterministic matching in a single flow. Hard rules for trusted identifiers like SSN, tax ID, email. Pass Through for records that should appear in output but not influence matching.

Everything in Lite, plus dictionary-based matching (nicknames, aliases, company name variants).

Match statistics and explainability for audit and governance.

Model operationsTrain once and match as data arrives.
  • Incremental runs update the identity graph as new records arrive without retraining.
  • Verify blocking before committing to matching.

Everything in Lite, plus

  • Upgrade models without disrupting Zingg IDs.
  • Compare two trained models side by side before deploying.
  • Re-assign IDs from an old model to a new one.
PlatformAny Spark environment - Databricks, Fabric, EMR, GCP Dataproc, and local Spark.All Community platforms plus
Native Snowflake run (no Spark cluster required)

All platforms in Lite.
### When to choose each edition -_Start with Community._ - -You get the full probabilistic matching engine, active learning, and support for every major Spark platform. Community runs on Databricks, Fabric, EMR, GCP Dataproc, or a local Docker container. +Community is the right place to start for many teams. You get the full probabilistic matching engine, active learning, and support for every major Spark platform. Community runs on Databricks, Fabric, AWS Glue, AWS EMR, GCP Dataproc, or a Docker container. -You can match any entity type, like customers, patients, suppliers, products, and citizens, on your own data with no time limit and no cost. +You can match any entity type, like customers, patients, suppliers, products, and citizens, on your own data with no record limit and no cost. -_The limitation is operational:_ Community's `Z Cluster` is non-persistent. If you re-run the match job, cluster IDs may change. You cannot safely store them in downstream systems. That is the boundary where Community ends and Enterprise begins. +_The limitation is operational:_ Community's `Z Cluster` is non-persistent. If you re-run the match job, cluster IDs may change. You cannot safely store them in downstream systems. That is the boundary where Community ends and Enterprise begins.\ +\ +_The limitation is also in the resolution:_ Compared to any other, Zingg Community is the most powerful entity resolution product on the planet. Zingg Enterprise is even more powerful.
From eb3688968ae2d8dfd227ea450adba62d1e90229e Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Sun, 16 Aug 2026 23:37:33 +0530 Subject: [PATCH 048/144] Snowflake label job service execution This commit updates the Snowflake training and labeling documentation to better reflect the actual workflow. It removes an outdated hint about the label phase, clarifies that `showConcise` is optional and only hides fields marked `NOT DONT_USE`, and adds the interactive Snowflake labeling service steps for running the label phase from a Snowflake service. --- docs/running-zingg/create-training-data.md | 2 +- docs/running-zingg/label-training-pairs.md | 46 ++++++++++++++++++---- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/docs/running-zingg/create-training-data.md b/docs/running-zingg/create-training-data.md index c3a93a10a..136db8da8 100644 --- a/docs/running-zingg/create-training-data.md +++ b/docs/running-zingg/create-training-data.md @@ -116,7 +116,7 @@ Zingg on Snowflake can be run either from a local terminal via the CLI, or nativ The `snowflake.properties` file supplies the connection details Zingg needs to reach your Snowflake account and run against your warehouse from outside Snowflake. {% hint style="info" icon="right-long" %} -Candidate pairs are written to `zinggDir/modelId`. Run the label phase next to review and label these pairs. +Candidate pairs are written to `zinggDir/modelId`. {% endhint %} #### Snowflake (job service) diff --git a/docs/running-zingg/label-training-pairs.md b/docs/running-zingg/label-training-pairs.md index 3a5ed3d13..1ea1d7d27 100644 --- a/docs/running-zingg/label-training-pairs.md +++ b/docs/running-zingg/label-training-pairs.md @@ -54,7 +54,7 @@ zingg.initAndExecute() ``` {% hint style="success" icon="right-long" %} -The `showConcise` flag only shows fields which are `NOT DONT_USE`. This makes the labelling session cleaner when you have many fields. +`--showConcise=true` is optional. It only shows fields which are `NOT DONT_USE`, making the labelling session cleaner when you have many fields. {% endhint %} ### `updateLabel` section @@ -82,7 +82,7 @@ This opens the console labeler, which accepts the cluster ID of the pairs you wa {% endtab %} {% tab title="Enterprise" %} -The `showConcise` flag only shows fields which are `NOT DONT_USE`. This makes the labelling session cleaner when you have many fields. +`--showConcise=true` is optional. It only shows fields which are `NOT DONT_USE`, making the labelling session cleaner when you have many fields. #### Python @@ -99,22 +99,54 @@ zingg.initAndExecute() Enterprise provides a visual widget showing one pair at a time with Match, No Match, and Can't Say buttons. Download the diagnostics view to share match quality with stakeholders before committing to training. {% endhint %} -_**CHECK WITH SONAL - Please confirm the exact Python code for the interactive label widget and saveMarkedRecords() call so this tab can be completed.**_ {% endtab %} {% tab title="Enterprise Snowflake" %} -The `showConcise` flag only shows fields which are `NOT DONT_USE`. This makes the labelling session cleaner when you have many fields. +`--showConcise=true` is optional. It only shows fields which are `NOT DONT_USE`, making the labelling session cleaner when you have many fields. ### Run label -#### CLI +Zingg on Snowflake can be run either from a local terminal via the CLI, or natively inside Snowflake using an interactive labeling service. + +#### CLI (local terminal) ```bash -./scripts/zingg.sh --phase label --conf config.json --showConcise=true \ --properties-file +./scripts/zingg.sh --phase label --conf config.json --showConcise=true \ +--properties-file +``` + +#### Snowflake (interactive service) + +The `label` phase is interactive, so instead of an async job, it runs as a standing service you connect to directly. + +```sql +CREATE SERVICE CONTAINER_ZINGG_DB.PUBLIC.ZINGG_CLI_SERVICE +IN COMPUTE POOL CONTAINER_ZINGG_POOL +FROM @specs +SPECIFICATION_FILE = '' +EXTERNAL_ACCESS_INTEGRATIONS = (ALLOW_ALL_EAI); +``` + +`` is the specification file that defines the container and service configuration used to run the Zingg CLI interactively inside Snowpark Container Services. It is maintained by your administrator and referenced from your `@specs` stage. + +Once the service is running, retrieve its endpoint to connect to the labeling session. + +```sql +SHOW ENDPOINTS IN SERVICE ZINGG_CLI_SERVICE; +``` + +Use the returned endpoint to open the interactive labeling terminal and label pairs as you would via CLI. + +**Stop the compute pool** + +Once you're done labeling, stop the compute pool to release resources. + +```sql +ALTER COMPUTE POOL CONTAINER_ZINGG_POOL STOP ALL; ``` {% hint style="info" icon="right-long" %} Enterprise provides a visual widget showing one pair at a time with Match, No Match, and Can't Say buttons. Download the diagnostics view to share match quality with stakeholders before committing to training. {% endhint %} {% endtab %} -{% endtabs %} +{% endtabs %} \ No newline at end of file From 86e49ef9d70defb05936a8fce560b1f57c096ed8 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Sun, 16 Aug 2026 18:35:41 +0000 Subject: [PATCH 049/144] GITBOOK-336: No subject --- docs/SUMMARY.md | 1 + .../zingg-concepts/how-zingg-learns/README.md | 71 +------------------ .../how-zingg-learns/zingg-active-learning.md | 55 ++++++++++++++ 3 files changed, 57 insertions(+), 70 deletions(-) create mode 100644 docs/zingg-concepts/how-zingg-learns/zingg-active-learning.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index f827fc38b..af7862c38 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -11,6 +11,7 @@ * [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) * [How Zingg Learns](zingg-concepts/how-zingg-learns/README.md) + * [Zingg Active Learning](zingg-concepts/how-zingg-learns/zingg-active-learning.md) * [Zingg Models](zingg-concepts/how-zingg-learns/zingg-models/README.md) * [Blocking Model](zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) * [Similarity Model](zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md) diff --git a/docs/zingg-concepts/how-zingg-learns/README.md b/docs/zingg-concepts/how-zingg-learns/README.md index 6b70571c4..92af98992 100644 --- a/docs/zingg-concepts/how-zingg-learns/README.md +++ b/docs/zingg-concepts/how-zingg-learns/README.md @@ -28,76 +28,7 @@ You label those pairs typically 30 to 50, as Match, No Match, or Uncertain. From Zingg models understand your data, and are not a generic algorithm applied to it. -### The active learning loop - -Zingg does not require thousands of pre-labeled training examples. It uses an active learning approach: it finds the record pairs where it is most uncertain, presents them to you, and learns from your labels. - -The loop is: - -{% stepper %} -{% step %} -### Step 1: `findTrainingData` - -Zingg scans your dataset and selects a set of candidate record pairs specifically chosen because they are the most uncertain and therefore the most informative for the model to learn from. Not random samples. The pairs are most likely to improve the model. -{% endstep %} - -{% step %} -### Step 2: `label` - -You review each pair and mark it as Match, No Match, or Uncertain. This is the only step in the Zingg workflow that requires human input. No ML knowledge needed; only\ -domain understanding of whether two records represent the same real-world entity. -{% endstep %} - -{% step %} -### Step 3: Repeat steps 1 and 2 - -Run `findTrainingData` and `label` again. Zingg surfaces a new set of candidate pairs; it is still uncertain about after learning from your previous labels. You repeat this loop until the pairs being surfaced align with your expectations. - -For most datasets, 30 to 50 labeled `match` pairs is a good starting point. The goal is not a specific count; it is coverage. Label until every field type in your schema is represented in your training data. -{% endstep %} - -{% step %} -### Step 4: `train` - -Once you are satisfied with your labeled pairs, run train. Zingg builds the blocking model and the similarity model from your labels. This step runs once per training cycle. -{% endstep %} -{% endstepper %} - -{% hint style="success" icon="right-long" %} -`findTrainingData` and `label` run multiple times in a loop before train ever runs. - -`train` runs once, after you have enough labeled pairs. -{% endhint %} - -### Why 30 to 50 labels are enough, and when to do more - -Standard supervised ML requires thousands or tens of thousands of labeled examples to reach\ -production accuracy. Active learning requires far less because the labels are chosen, not random. - -Every pair of Zingg surfaces for labeling is specifically selected because it is uncertain given what the model already knows. Each label you add is maximally informative. There is no noise from irrelevant or redundant examples. - -The practical implication: a well-chosen set of 30 to 50 match labels produces a similarity model that generalizes to your full dataset. - -More labels improve accuracy, but the return diminishes quickly. The more important variable is coverage. `Label` until your training set includes examples of all the variation patterns in your data - different name formats, address abbreviations, missing fields; not until you reach a specific number. - -If match performance needs improvement after your first run, return to the label loop. Focus on the patterns your results show are missing or underperforming. - -### The two models Zingg builds - -When you run `train`, Zingg builds two separate models from your `labeled` pairs. Each solves a different part of the entity resolution problem. - -{% hint style="success" icon="right-long" %} -The blocking model and the similarity model are covered in full on their own pages\ -with task-level detail for inspection, tuning, and debugging. - -* [Blocking model](zingg-models/blocking-model.md) -* [Similarity model](zingg-models/similarity-model.md) -* [Zingg Models](zingg-models/) (parent page) - -The graph algorithm that turns pairwise match decisions into complete clusters is also covered in the Zingg Models section. -{% endhint %} - -### Quick reference on which match type for which field +### Quick reference on which match type for which field - TODO Move When you define your field configuration, you choose a match type for each field. Here is a starting point for the most common field types. diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-active-learning.md b/docs/zingg-concepts/how-zingg-learns/zingg-active-learning.md new file mode 100644 index 000000000..9d43faa23 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/zingg-active-learning.md @@ -0,0 +1,55 @@ +# Zingg Active Learning + +### The active learning loop + +Zingg does not require thousands of pre-labeled training examples. It uses an active learning approach: it finds the record pairs where it is most uncertain, presents them to you, and learns from your labels. + +The loop is: + +{% stepper %} +{% step %} +### Step 1: `findTrainingData` + +Zingg scans your dataset and selects a set of candidate record pairs specifically chosen because they are the most uncertain and therefore the most informative for the model to learn from. Not random samples. The pairs are most likely to improve the model. +{% endstep %} + +{% step %} +### Step 2: `label` + +You review each pair and mark it as Match, No Match, or Uncertain. This is the only step in the Zingg workflow that requires human input. No ML knowledge needed; only\ +domain understanding of whether two records represent the same real-world entity. +{% endstep %} + +{% step %} +### Step 3: Repeat steps 1 and 2 + +Run `findTrainingData` and `label` again. Zingg surfaces a new set of candidate pairs; it is still uncertain about after learning from your previous labels. You repeat this loop until the pairs being surfaced align with your expectations. + +For most datasets, 30 to 50 labeled `match` pairs is a good starting point. The goal is not a specific count; it is coverage. Label until every field type in your schema is represented in your training data. +{% endstep %} + +{% step %} +### Step 4: `train` + +Once you are satisfied with your labeled pairs, run train. Zingg builds the blocking model and the similarity model from your labels. This step runs once per training cycle. +{% endstep %} +{% endstepper %} + +{% hint style="success" icon="right-long" %} +`findTrainingData` and `label` run multiple times in a loop before train ever runs. + +`train` runs once, after you have enough labeled pairs. +{% endhint %} + +### Why 30 to 50 labels are enough, and when to do more + +Standard supervised ML requires thousands or tens of thousands of labeled examples to reach\ +production accuracy. Active learning requires far less because the labels are chosen, not random. + +Every pair of Zingg surfaces for labeling is specifically selected because it is uncertain given what the model already knows. Each label you add is maximally informative. There is no noise from irrelevant or redundant examples. + +The practical implication: a well-chosen set of 30 to 50 match labels produces a similarity model that generalizes to your full dataset. + +More labels improve accuracy, but the return diminishes quickly. The more important variable is coverage. `Label` until your training set includes examples of all the variation patterns in your data - different name formats, address abbreviations, missing fields; not until you reach a specific number. + +If match performance needs improvement after your first run, return to the label loop. Focus on the patterns your results show are missing or underperforming. From d8430d5d5c5cacd6d5380aa134bc740a2a03a21a Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Mon, 17 Aug 2026 10:59:02 +0530 Subject: [PATCH 050/144] Document Snowflake train job flow Updated the training docs to add a dedicated Snowflake section with both CLI and async job service instructions for running train. The change includes SQL examples for launching a job, checking logs, and stopping the compute pool, and removes the generic blocking-model note from the Snowflake-specific guidance. --- .../running-zingg/build-and-save-the-model.md | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/docs/running-zingg/build-and-save-the-model.md b/docs/running-zingg/build-and-save-the-model.md index b44ffd385..b29acf975 100644 --- a/docs/running-zingg/build-and-save-the-model.md +++ b/docs/running-zingg/build-and-save-the-model.md @@ -53,13 +53,6 @@ zingg.initAndExecute() ```bash ./scripts/zingg.sh --phase train --conf config.json ``` - -Enterprise adds blocking model configuration via `args.setBlockingModel()`. Set this in Configure Zingg before running `train`. - -Available options: - -* `DEFAULT` - standard blocking strategy, suitable for most datasets -* `WIDER` - casts a wider blocking net; use if known matching pairs are being missed {% endtab %} {% tab title="Enterprise Snowflake" %} @@ -67,20 +60,50 @@ Available options: Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark cluster. {% endhint %} +### Run train -### CLI +Zingg on Snowflake can be run either from a local terminal via the CLI, or natively inside Snowflake as a job service. + +#### CLI (local terminal) ```bash ./scripts/zingg.sh --phase train --conf config.json \ --properties-file ``` -Enterprise adds blocking model configuration via `"modelId": "FEBRL120K"`. Set this in Configure Zingg before running `train`. +#### Snowflake (job service) + +Run the phase as an asynchronous job service inside your Snowflake compute pool. + +```sql +EXECUTE JOB SERVICE +IN COMPUTE POOL CONTAINER_ZINGG_POOL +NAME = ZINGG_TRAIN_ASYNC_JOB_SERVICE +ASYNC = true +EXTERNAL_ACCESS_INTEGRATIONS = (ALLOW_ALL_EAI) +FROM @specs SPECIFICATION_TEMPLATE_FILE = '' +USING (PHASE => 'train', CONFIG => ''); +``` + +`` defines the container and job service configuration Zingg needs to run inside Snowpark Container Services. It is maintained by your administrator and referenced from your `@specs` stage. + +`` is the name of your Zingg configuration json file. + +**Check the job logs to monitor progress or troubleshoot errors.** + +```sql +SELECT SYSTEM$GET_SERVICE_LOGS('ZINGG_TRAIN_ASYNC_JOB_SERVICE', 0, 'zingg-async-job-container'); +``` + +`zingg-async-job-container` refers to the container name defined in the job specification. -Available options: +**Stop the compute pool** -* `DEFAULT` - standard blocking strategy, suitable for most datasets -* `WIDER` - casts a wider blocking net; use if known matching pairs are being missed +Once the job completes, stop the compute pool to release resources. + +```sql +ALTER COMPUTE POOL CONTAINER_ZINGG_POOL STOP ALL; +``` {% endtab %} -{% endtabs %} +{% endtabs %} \ No newline at end of file From 8ad8e18a9e8ff96bea99bf6a887287ddadbcf152 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Mon, 17 Aug 2026 05:31:24 +0000 Subject: [PATCH 051/144] GITBOOK-337: No subject --- docs/zingg-concepts/zingg-entity-resolution-platform/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/README.md b/docs/zingg-concepts/zingg-entity-resolution-platform/README.md index 016af6154..50893e32f 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/README.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/README.md @@ -4,7 +4,7 @@ Zingg is an ML-powered entity resolution engine built to run where your data alr Three capabilities work together to handle the full problem: -
Warehouse-native executionProbabilistic + deterministic matchingPersistent identity graph
Zingg runs inside Databricks, Microsoft
Fabric, Snowflake, GCP Dataproc, AWS Glue, and AWS EMR. Your data never
leaves your environment. No ETL pipelines. No external APIs. There is no separate infrastructure to operate. The same model that runs on 100,000 records scales to hundreds of millions using your existing Spark or Snowflake compute, without any architectural changes.

Probabilistic matching is Zingg's default and available in the Community Edition. The ML model learns from your labeled pairs; 30 to 50 examples are enough to build a model calibrated
to your specific data and scores every candidate pair on multiple field-level features. It handles
typos, abbreviations, missing values, and format variations automatically.

Deterministic matching (Enterprise) adds hard rules for trusted identifiers.
When two records share the same SSN, tax ID, or email, Zingg treats them
as the same entity without consulting the ML model. Both approaches run in a single flow.

Deterministic vs Probabilistic
Matching

Every resolved entity receives a Zingg ID, a globally unique, persistent GUID assigned in Enterprise that remains stable
across runs, incremental updates, and model changes.

Community produces a Z Cluster
which is unique. Matching records share the same Z_Cluster that is reassigned each run.


Enterprise produces a Zingg ID you can store in downstream systems
with confidence.

The identity graph grows incrementally. New records are matched to existing clusters without rerunning on your full dataset.

Identity Graph
Z Cluster and Zingg ID

+
Warehouse-native executionProbabilistic + deterministic matchingPersistent identity graph
Zingg runs inside Databricks, Microsoft
Fabric, Snowflake, GCP Dataproc, AWS Glue, and AWS EMR. Your data never
leaves your environment. No ETL pipelines. No external APIs. There is no separate infrastructure to operate. The same model that runs on 100,000 records scales to hundreds of millions using your existing Spark or Snowflake compute, without any architectural changes.

Probabilistic matching is Zingg's default and available in the Community Edition. The ML model learns from your labeled pairs; 30 to 50 examples are enough to build a model calibrated
to your specific data and scores every candidate pair on multiple field-level features. It handles
typos, abbreviations, missing values, and format variations automatically.

Deterministic matching (Enterprise) adds hard rules for trusted identifiers.
When two records share the same SSN, tax ID, or email, Zingg treats them
as the same entity without consulting the ML model.

Both approaches run in a single flow, finding all possible matches.

Deterministic vs Probabilistic
Matching

Every resolved entity receives a Zingg ID, a globally unique, persistent GUID assigned in Enterprise that remains stable
across runs, incremental updates, and model changes.

Community produces a Z Cluster
which is unique. Matching records share the same Z_Cluster that is reassigned each run.


Enterprise produces a Zingg ID you can store in downstream systems
with confidence.

The identity graph grows incrementally. New records are matched to existing clusters without rerunning on your full dataset.

Identity Graph
Z Cluster and Zingg ID

This is the combination that makes enterprise-scale entity resolution computationally feasible and practically maintainable. From 5c943cce462aa5add4554cc6c41cd452cf822bdf Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Mon, 17 Aug 2026 05:35:15 +0000 Subject: [PATCH 052/144] GITBOOK-338: No subject --- .../deterministic-vs-probabilistic-matching.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md b/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md index 713b5daa9..7ea385990 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md @@ -20,15 +20,13 @@ _You do not write rules. You label examples. Zingg learns the rest._ ### Deterministic matching -Deterministic matching lets you define hard rules for cases where certain field combinations should always produce a match, regardless of the probabilistic score. When your data contains reliable unique identifiers, a national ID, an email, or a combination like first name plus date of birth, +Deterministic matching lets you define hard rules for cases where certain field combinations should always produce a match, regardless of the probabilistic score. When your data contains reliable unique identifiers, a national ID, an email, or a combination like first name, email plus date of birth, determinstic matching can bring them together. + -If two records share the same combination of identifiers you specify, Zingg treats that pair as a match with a score of 1, bypassing the ML model entirely for that pair. ### How Zingg matches prbabilistically and deterministically -Zingg Enterprise applies deterministic rules first. Pairs resolved deterministically are not re-evaluated probabilistically. Pairs that do not satisfy any deterministic condition fall through to the probabilistic model. Both paths produce output in the same format.\ -\ -Zingg Enterprise can apply deterministic rules on top of the probabilistic model. Pairs that satisfy a deterministic condition are resolved with a score of 1, without consulting the ML model. Pairs that do not satisfy any condition fall through to probabilistic matching as normal. All matching pairs get rolled into clusters. +Zingg Enterprise applies deterministic rules first. If two records share the same combination of identifiers you specify, Zingg treats that pair as a match with a score of 1, bypassing the ML model entirely for that pair. Pairs resolved deterministically are not re-evaluated probabilistically. Pairs that do not satisfy any deterministic condition fall through to the probabilistic model. All matching pairs get rolled into clusters. Probabilistic and deterministic matching in Zingg Enterprise runs both in a single flow, so you do not have to choose between them. From 4466a3a8c9260c4637724d709d703d19eabfbd43 Mon Sep 17 00:00:00 2001 From: padam Date: Mon, 17 Aug 2026 05:49:16 +0000 Subject: [PATCH 053/144] GITBOOK-339: No subject --- docs/running-zingg/run-the-match-phase.md | 41 ++--------------------- 1 file changed, 3 insertions(+), 38 deletions(-) diff --git a/docs/running-zingg/run-the-match-phase.md b/docs/running-zingg/run-the-match-phase.md index ddcf7952b..71d5f2778 100644 --- a/docs/running-zingg/run-the-match-phase.md +++ b/docs/running-zingg/run-the-match-phase.md @@ -10,15 +10,6 @@ The `match` phase runs AFTER `train`. It applies the trained Zingg model to your Use `match` when you want to find duplicates within a single dataset. If you need to match records across two separate datasets, use the link phase instead - both are equal operations using the same trained model. -### Output Fields - -Every record in the match output contains your original input fields plus three columns added by Zingg: - -* `Z_CLUSTER` - unique identifier shared by all records in the same cluster. Records with the same `Z_CLUSTER` represent the same real-world entity. In Enterprise, this is the persistent\ - Zingg ID. -* `Z_MINSCORE` - the lowest similarity score between any two records in that cluster. Indicates the confidence of the weakest link in the cluster. -* `Z_MAXSCORE` - the highest similarity score between any two records in that cluster. Indicates the strongest match within the cluster. - {% hint style="success" icon="right-long" %} **Read more:** @@ -48,17 +39,12 @@ zingg.initAndExecute() ### Read and View Output ```python -output = spark.read.csv( - "/tmp/febrlOutput", - header = True -) +output = spark.read.csv("path-to-output-directory",header = True) display(output) ``` {% hint style="info" icon="right-long" %} Matching records share the same `Z_CLUSTER` value. - -`Z_MINSCORE` and `Z_MAXSCORE` show match confidence within the cluster. {% endhint %} {% endtab %} @@ -66,10 +52,7 @@ Matching records share the same `Z_CLUSTER` value. ### Python ```python -options = ClientOptions([ - ClientOptions.PHASE, - "match" -]) +options = ClientOptions([ClientOptions.PHASE,"match"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` @@ -84,10 +67,7 @@ zingg.initAndExecute() ```python # Read match output -output = spark.read.csv( - "/tmp/febrlOutput", - header=True -) +output = spark.read.csv("path-to-output-directory",header=True) display(output) ``` @@ -101,7 +81,6 @@ Enterprise output includes `Zingg ID` (persistent across runs) instead of `Z_CLU Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark cluster. {% endhint %} - ### CLI ```bash @@ -112,19 +91,5 @@ Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark c {% hint style="info" icon="right-long" %} Enterprise output includes `Zingg ID` (persistent across runs) instead of `Z_CLUSTER`, plus deterministic match flag and Match Statistics. `Zingg ID` is stable across all subsequent incremental runs. {% endhint %} - {% endtab %} {% endtabs %} - -
- -How do I interpret Z_MINSCORE and Z_MAXSCORE? - -`Z_MINSCORE` and `Z_MAXSCORE` are the confidence range for a cluster. - -* `Z_MINSCORE` is the lowest similarity score between any two records in the cluster. A very low `Z_MINSCORE` means some records in the cluster matched weakly—worth reviewing manually. -* `Z_MAXSCORE` is the highest similarity score between any pair in the cluster. The threshold is automatically optimized by Zingg so you do not need to tune a cut-off manually. You may see records with scores below the conventional 0.5; this behaviour is intentional as Zingg optimizes for both accuracy and recall. - -**Recommended approach**: Keep clusters whose value `Z_MINSCORE` is 0 for manual inspection. Keep the cluster size above 4 or 5 for closer review. The exact threshold depends on how accurate you determine your results and how much manual control you want. - -
From 3f2991503778019c5bee1980e75f51fa6b971527 Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Mon, 17 Aug 2026 11:44:57 +0530 Subject: [PATCH 054/144] Document Snowflake job-service workflows for match and incremental This update revises the Snowflake documentation for Zingg to match the current enterprise workflow. It removes outdated Enterprise-only notes, adds CLI and async job service instructions for training, match, and incremental matching, and clarifies labeling guidance and job monitoring steps for Snowflake execution. --- .../running-zingg/build-and-save-the-model.md | 7 ++- docs/running-zingg/label-training-pairs.md | 7 ++- docs/running-zingg/link-across-datasets.md | 3 -- .../running-zingg/run-incremental-matching.md | 42 +++++++++++++++- docs/running-zingg/run-the-match-phase.md | 49 +++++++++++++++++-- 5 files changed, 91 insertions(+), 17 deletions(-) diff --git a/docs/running-zingg/build-and-save-the-model.md b/docs/running-zingg/build-and-save-the-model.md index b29acf975..b53ae225b 100644 --- a/docs/running-zingg/build-and-save-the-model.md +++ b/docs/running-zingg/build-and-save-the-model.md @@ -56,9 +56,6 @@ zingg.initAndExecute() {% endtab %} {% tab title="Enterprise Snowflake" %} -{% hint style="info" icon="right-long" %} -Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark cluster. -{% endhint %} ### Run train @@ -89,7 +86,9 @@ USING (PHASE => 'train', CONFIG => ''); `` is the name of your Zingg configuration json file. -**Check the job logs to monitor progress or troubleshoot errors.** +****Monitor the job** + +Retrieve the service logs to monitor execution and debug any failures.** ```sql SELECT SYSTEM$GET_SERVICE_LOGS('ZINGG_TRAIN_ASYNC_JOB_SERVICE', 0, 'zingg-async-job-container'); diff --git a/docs/running-zingg/label-training-pairs.md b/docs/running-zingg/label-training-pairs.md index 1ea1d7d27..90e545640 100644 --- a/docs/running-zingg/label-training-pairs.md +++ b/docs/running-zingg/label-training-pairs.md @@ -96,20 +96,19 @@ zingg.initAndExecute() ``` {% hint style="info" icon="right-long" %} -Enterprise provides a visual widget showing one pair at a time with Match, No Match, and Can't Say buttons. Download the diagnostics view to share match quality with stakeholders before committing to training. +Notebook provides a visual widget showing one pair at a time with Match, No Match, and Can't Say buttons. {% endhint %} {% endtab %} {% tab title="Enterprise Snowflake" %} -`--showConcise=true` is optional. It only shows fields which are `NOT DONT_USE`, making the labelling session cleaner when you have many fields. ### Run label Zingg on Snowflake can be run either from a local terminal via the CLI, or natively inside Snowflake using an interactive labeling service. #### CLI (local terminal) - +`--showConcise=true` is optional. It only shows fields which are `NOT DONT_USE`, making the labelling session cleaner when you have many fields. ```bash ./scripts/zingg.sh --phase label --conf config.json --showConcise=true \ --properties-file @@ -146,7 +145,7 @@ ALTER COMPUTE POOL CONTAINER_ZINGG_POOL STOP ALL; ``` {% hint style="info" icon="right-long" %} -Enterprise provides a visual widget showing one pair at a time with Match, No Match, and Can't Say buttons. Download the diagnostics view to share match quality with stakeholders before committing to training. +Notebook provides a visual widget showing one pair at a time with Match, No Match, and Can't Say buttons. {% endhint %} {% endtab %} {% endtabs %} \ No newline at end of file diff --git a/docs/running-zingg/link-across-datasets.md b/docs/running-zingg/link-across-datasets.md index 696a35bbe..3710b9385 100644 --- a/docs/running-zingg/link-across-datasets.md +++ b/docs/running-zingg/link-across-datasets.md @@ -96,9 +96,6 @@ Enterprise link output includes Zingg ID instead of `Z_CLUSTER`, plus the `Z_SOU {% endtab %} {% tab title="Enterprise Snowflake" %} -{% hint style="info" icon="right-long" %} -Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark cluster. -{% endhint %} ### CLI diff --git a/docs/running-zingg/run-incremental-matching.md b/docs/running-zingg/run-incremental-matching.md index 0b868eb0c..f509d2687 100644 --- a/docs/running-zingg/run-incremental-matching.md +++ b/docs/running-zingg/run-incremental-matching.md @@ -227,6 +227,10 @@ The `outputTmp` section specifies a temporary output location where Zingg writes {% tab title="Enterprise Snowflake" %} +Zingg on Snowflake can be run either from a local terminal via the CLI, or natively inside Snowflake as a job service. + +### CLI (local terminal) + Create an `incrementalConf.json` file: ```json @@ -261,5 +265,41 @@ Create an `incrementalConf.json` file: The `outputTmp` section specifies a temporary output location where Zingg writes intermediate results before final processing or merging with the main output. +### Snowflake (job service) + +Run the phase as an asynchronous job service inside your Snowflake compute pool. + +```sql +EXECUTE JOB SERVICE +IN COMPUTE POOL CONTAINER_ZINGG_POOL +NAME = ZINGG_INCREMENTAL_ASYNC_JOB_SERVICE +ASYNC = true +EXTERNAL_ACCESS_INTEGRATIONS = (ALLOW_ALL_EAI) +FROM @specs SPECIFICATION_TEMPLATE_FILE = '' +USING (PHASE => 'runIncremental', CONFIG => ''); +``` + +`` defines the container and job service configuration Zingg needs to run inside Snowpark Container Services. It is maintained by your administrator and referenced from your `@specs` stage. + +`` is the name of your incremental configuration json file (e.g. `incrementalConf.json`). + +**Monitor the job** + +Retrieve the service logs to monitor execution and debug any failures. + +```sql +SELECT SYSTEM$GET_SERVICE_LOGS('ZINGG_INCREMENTAL_ASYNC_JOB_SERVICE', 0, 'zingg-async-job-container'); +``` + +`zingg-async-job-container` refers to the container name defined in the job specification. + +**Stop the compute pool** + +Once the job completes, stop the compute pool to release resources. + +```sql +ALTER COMPUTE POOL CONTAINER_ZINGG_POOL STOP ALL; +``` + {% endtab %} -{% endtabs %} +{% endtabs %} \ No newline at end of file diff --git a/docs/running-zingg/run-the-match-phase.md b/docs/running-zingg/run-the-match-phase.md index 71d5f2778..e0e048e52 100644 --- a/docs/running-zingg/run-the-match-phase.md +++ b/docs/running-zingg/run-the-match-phase.md @@ -77,19 +77,58 @@ Enterprise output includes `Zingg ID` (persistent across runs) instead of `Z_CLU {% endtab %} {% tab title="Enterprise Snowflake" %} -{% hint style="info" icon="right-long" %} -Enterprise only. Zingg on Snowflake uses Snowpark and does not require a Spark cluster. -{% endhint %} -### CLI +### Run match + +Zingg on Snowflake can be run either from a local terminal via the CLI, or natively inside Snowflake as a job service. + +#### CLI (local terminal) ```bash ./scripts/zingg.sh --phase match --conf config.json \ --properties-file ``` +#### Snowflake (job service) + +Run the phase as an asynchronous job service inside your Snowflake compute pool. + +```sql +EXECUTE JOB SERVICE +IN COMPUTE POOL CONTAINER_ZINGG_POOL +NAME = ZINGG_MATCH_ASYNC_JOB_SERVICE +ASYNC = true +EXTERNAL_ACCESS_INTEGRATIONS = (ALLOW_ALL_EAI) +FROM @specs SPECIFICATION_TEMPLATE_FILE = '' +USING (PHASE => 'match', CONFIG => ''); +``` + +`` defines the container and job service configuration Zingg needs to run inside Snowpark Container Services. It is maintained by your administrator and referenced from your `@specs` stage. + +`` is the name of your Zingg configuration json file. + +**Monitor the job** + +Retrieve the service logs to monitor execution and debug any failures. + +```sql +SHOW SERVICE CONTAINERS IN SERVICE ZINGG_MATCH_ASYNC_JOB_SERVICE; + +SELECT SYSTEM$GET_SERVICE_LOGS('ZINGG_MATCH_ASYNC_JOB_SERVICE', 0, 'zingg-async-job-container'); +``` + +`zingg-async-job-container` refers to the container name defined in the job specification. + +**Stop the compute pool** + +Once the job completes, stop the compute pool to release resources. + +```sql +ALTER COMPUTE POOL CONTAINER_ZINGG_POOL STOP ALL; +``` + {% hint style="info" icon="right-long" %} Enterprise output includes `Zingg ID` (persistent across runs) instead of `Z_CLUSTER`, plus deterministic match flag and Match Statistics. `Zingg ID` is stable across all subsequent incremental runs. {% endhint %} {% endtab %} -{% endtabs %} +{% endtabs %} \ No newline at end of file From c3faca99fd371c8120f272a99d487f77b1ac5ced Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Mon, 17 Aug 2026 13:00:21 +0530 Subject: [PATCH 055/144] Add Blocking Strategies doc and cross-links Add a new docs page explaining the Enterprise-only blockingModel strategies (DEFAULT vs WIDER) and how they affect blocking-tree field ordering. Wire the new page into SUMMARY.md and add contextual links from configure-zingg, verify-blocking, custom-blocking-and-similarity, and the blocking-model concept doc. Also remove an extraneous SHOW SERVICE CONTAINERS line from run-the-match-phase.md. --- docs/SUMMARY.md | 1 + docs/running-zingg/configure-zingg.md | 2 +- docs/running-zingg/run-the-match-phase.md | 1 - docs/running-zingg/verify-blocking.md | 1 + docs/tuning/blocking-strategy.md | 74 +++++++++++++++++++ docs/tuning/custom-blocking-and-similarity.md | 4 + .../zingg-models/blocking-model.md | 1 + 7 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 docs/tuning/blocking-strategy.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index af7862c38..7f34f5455 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -87,6 +87,7 @@ * [Improve Accuracy](tuning/improve-accuracy/README.md) * [Remove Stopwords (Optional)](tuning/improve-accuracy/remove-stopwords-optional.md) * [Custom Blocking and Similarity](tuning/custom-blocking-and-similarity.md) +* [Blocking Strategies: DEFAULT vs WIDER](tuning/blocking-strategy.md) ## Interpreting Results diff --git a/docs/running-zingg/configure-zingg.md b/docs/running-zingg/configure-zingg.md index c8e7ed46d..a92b0afff 100644 --- a/docs/running-zingg/configure-zingg.md +++ b/docs/running-zingg/configure-zingg.md @@ -269,7 +269,7 @@ args.setNumPartitions(4) args.setLabelDataSampleSize(0.5) ``` -Set the blocking strategy directly after `setLabelDataSampleSize`. If not set, the model follows `DEFAULT`. +Set the blocking strategy if needed. If not set, the model follows `DEFAULT`. See [Blocking Strategies: DEFAULT vs WIDER](../tuning/blocking-strategy.md) for how each strategy orders fields. #### Python diff --git a/docs/running-zingg/run-the-match-phase.md b/docs/running-zingg/run-the-match-phase.md index e0e048e52..56f7efb95 100644 --- a/docs/running-zingg/run-the-match-phase.md +++ b/docs/running-zingg/run-the-match-phase.md @@ -112,7 +112,6 @@ USING (PHASE => 'match', CONFIG => ''); Retrieve the service logs to monitor execution and debug any failures. ```sql -SHOW SERVICE CONTAINERS IN SERVICE ZINGG_MATCH_ASYNC_JOB_SERVICE; SELECT SYSTEM$GET_SERVICE_LOGS('ZINGG_MATCH_ASYNC_JOB_SERVICE', 0, 'zingg-async-job-container'); ``` diff --git a/docs/running-zingg/verify-blocking.md b/docs/running-zingg/verify-blocking.md index 0460d7664..fb8f23084 100644 --- a/docs/running-zingg/verify-blocking.md +++ b/docs/running-zingg/verify-blocking.md @@ -91,6 +91,7 @@ If `verifyBlocking` shows that many known matching pairs are not being blocked t 1. **Add more labelled training pairs** of the type being missed. The blocking model learns from the same training data as the similarity model. 2. **Check your field match types.** Fields set to `DONT_USE` are excluded from blocking. If a key identity field is set to `DONT_USE`, matching pairs that differ on other fields may end up in different blocks. +3. **Try the `WIDER` blocking strategy** (Enterprise only) if large blocks suggest the tree is over-relying on one or two fields → [Blocking Strategies: DEFAULT vs WIDER](../tuning/blocking-strategy.md) {% hint style="success" icon="right-long" %} **Consider custom blocking functions** for specialised data patterns → [Custom Blocking and Similarity](../tuning/custom-blocking-and-similarity.md) diff --git a/docs/tuning/blocking-strategy.md b/docs/tuning/blocking-strategy.md new file mode 100644 index 000000000..0085c2cd1 --- /dev/null +++ b/docs/tuning/blocking-strategy.md @@ -0,0 +1,74 @@ +--- +description: >- + Choose between DEFAULT and WIDER blocking strategies to control how Zingg + orders candidate fields when building the blocking tree. +--- + +# Blocking Strategies: DEFAULT vs WIDER + +**Enterprise only.** Controlled by the `blockingModel` config key (`DEFAULT` or `WIDER`, case-insensitive). If omitted, Zingg uses `DEFAULT`. Community/OSS has no such key - it always behaves like `DEFAULT`. + +## What each strategy does + +Zingg builds a blocking tree greedily. At every node it asks: *of the eligible field/hash-function combinations , which one splits the group into smaller pieces?* That combination becomes the split at this node, and the process recurses into each resulting group. + +The **strategy** controls only the *order* candidate fields are offered at each node - never the comparison logic itself: + +* **`DEFAULT`** - every node is offered your `fieldDefinition` list in the exact order you configured it. The field listed first is always tried first, everywhere in the tree. +* **`WIDER`** - each node looks at which field its *parent* node just used, and pushes that field to the back of the candidate list for this node, so the very next field in line gets first consideration instead. This repeats going down the tree, so the field that "won" one level isn't automatically favored again immediately below it. + +## When to use which + +
+ +Use DEFAULT when your fields have very different discriminating power + +If one field (for example, a national ID or a well-populated postcode) is consistently the best splitter for your data, `DEFAULT` lets that field win at every node where it's eligible. This produces a narrower, more predictable tree built around your strongest signal. + +
+ +
+ +Use WIDER when several fields have similar discriminating power + +If your first field only slightly outperforms the second and third (for example, `fname`, `lname`, and `city` all split the data reasonably well), `DEFAULT` can end up leaning on the same field repeatedly down consecutive levels, producing a tree that is deep in one dimension and under-uses the others. `WIDER` forces the next-best field into consideration at the child node, spreading splits across more fields and typically producing a wider, shallower tree. + +
+ +
+ +Use WIDER if verifyBlocking shows large, low-specificity blocks + +Large blocks with `DEFAULT` are a symptom of the tree over-relying on one or two fields. Try `WIDER` and re-run `verifyBlocking` to compare block-size distribution and coverage before committing to the change. + +
+ +## Configure + +Set `blockingModel` right after `labelDataSampleSize` when building your Enterprise arguments object. + +{% tabs %} +{% tab title="Python" %} +```python +args.setBlockingModel("WIDER") +``` +{% endtab %} + +{% tab title="JSON" %} +```json +{ + "fieldDefinition": [ ... ], + "blockingModel": "WIDER" +} +``` +{% endtab %} +{% endtabs %} + +{% hint style="success" icon="right-long" %} +**Read more**: + +* Set up field definitions and the arguments object this key belongs to → [Configure Zingg](../running-zingg/configure-zingg.md) +* Check block-size distribution and coverage after changing strategy → [Verify Blocking](../running-zingg/verify-blocking.md) +* Blocking model concept and how it fits in the pipeline → [Blocking Model](../zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) +* Define your own blocking functions for specialized data patterns → [Custom Blocking and Similarity](custom-blocking-and-similarity.md) +{% endhint %} diff --git a/docs/tuning/custom-blocking-and-similarity.md b/docs/tuning/custom-blocking-and-similarity.md index 2b84ea47e..5f4441439 100644 --- a/docs/tuning/custom-blocking-and-similarity.md +++ b/docs/tuning/custom-blocking-and-similarity.md @@ -66,3 +66,7 @@ _**LINK TO BE ADDED - GitHub Zingg repository custom similarity example (add li Custom similarity and blocking function implementation requires Java/Scala code changes to the Zingg JAR. These are advanced customizations. For most use cases, adjusting match types, stopwords, and training data is sufficient before reaching this step. +{% hint style="success" icon="right-long" %} +**Read more**: Before writing custom functions, try switching the field ordering strategy at each blocking-tree node → [Blocking Strategies: DEFAULT vs WIDER](blocking-strategy.md) (Enterprise only) +{% endhint %} + diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md index c0cf40432..03dc57ce3 100644 --- a/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md @@ -86,5 +86,6 @@ influence matching. * [Label Training Pairs](../../../running-zingg/label-training-pairs.md) - how to add more training data * [Configure Zingg](../../../running-zingg/configure-zingg.md) - changing field match types +* [Blocking Strategies: DEFAULT vs WIDER](../../../tuning/blocking-strategy.md) - reorder candidate fields in the blocking tree (Enterprise only) * [Custom Blocking and Similarity](../../../tuning/custom-blocking-and-similarity.md) Functions {% endhint %} From a2c8d813ff6c660eccf053edfc42fd92b53a538e Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Mon, 17 Aug 2026 13:24:07 +0530 Subject: [PATCH 056/144] Add blocking strategy docs and diagram Document the enterprise-only blockingModel behavior for DEFAULT vs WIDER and clarify how the blocking tree selects split fields. This adds a visual tree diagram showing field-ordering differences down the tree and updates the wording around candidate selection and strategy semantics. --- docs/.gitbook/assets/blocking-strategy-tree.png | Bin 0 -> 58831 bytes docs/tuning/blocking-strategy.md | 6 +++++- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 docs/.gitbook/assets/blocking-strategy-tree.png diff --git a/docs/.gitbook/assets/blocking-strategy-tree.png b/docs/.gitbook/assets/blocking-strategy-tree.png new file mode 100644 index 0000000000000000000000000000000000000000..645243a0acada481de6f345dd7b31ecb7d4f6e6b GIT binary patch literal 58831 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˭ec3rW0=Vov&-y8V_A*$t=gXHC0L+y_Dl;Yfuca51c@O z3F2 z8t9ipH=e%3PVx`6%1z8|r+B*er4kPNVeL)VFMew~QkQCf(UBJl0Orau(X9$<0OuAO zg+wJcORsg@-zaJ6!+oD?ht>Sh0phu(H#js344K8rkno%4^7W!ea03supk(=jD2M6k z4#~r+^0@{~dvb2q#T+feoVG1e3lH4Yx40O;bD-?jAw&fxKK{miwxE$2 zD}PT!yGymbP`nkllfGcaw4*a$a7NFPh&MG0V+ z>km5!_#GgEzuakslj=Bb{ZL$>1N!lIRFy!u`sS+zNd{W+|t4kU%@vB7mU+*9A!$ zBfgrp+>9P!X$3#Xbe@|Yc2C|MTXm6zp~UYHM}5@}vGr>zil%>BLeKx2|KL{xA}PX; z@Z}?5KpsXTccLNCi=N#m1=BJq#xse!ltm0wdya7rv*$Jx|7*JeOE>>hIpZfVChj7S z{PP0n5^R4215Zly{V_YQA;pb^y$SUxsgh3Z|Jo`YxL=xU*wRihsOLaa+FrsI@RQ;+ zSXoavAATH2YO@H`O(0Ls#4~f^5Jc2pCcHowiPDO{>c-EHfNmU>35gQ$y%wA($0(># z^E=0F>(7^LzJ{tgtB#AOrNpI8(G&EW?k~DKcP0;Gs`)FJWii2lL$}}YY2@DltAMt8 zr>6$euEg1mST7U{#m>|BHq!{3%HA)~cZSK?+E#qtd$d})LdGKd@z<4nO9DVIB%YM$ zEDYcdx=ta+QHx^T<1Lh}hkpB^63b$pN(wUM zD1dW@qGTS{B;1o@m^-3)-TcEV*0`q!G&Vu^D|A4A&x!NgI0=qn{3Ci>1*3pI;M4B@Xzh;`{unACS@i{?$?LnbiN?kcszQ(lT9f1$z4Gtt%WlOv3| zGYT}?sKcs9F0_N_%PY)TxjmJzQFothd*&}mDG#35pNsM#3w<392_`TcWEeBgU?uoa zR>008ylFPs-ZGMr4L(yOvRbq2lS_j*DnfHOUPmx6Q%hNxTy3Rp;qws~ZLd+hy6OD= zYy~q)QM+*TB?enb8XXGIJy9q?%gM8o@}o0I0a!HuBejlBS=?e~H^aqqF`Q&lO&Fn#%c%C%#a1nAOp97^1z#$R zSZ~iF>b&j0cslquIbA6}JX04fwUz&76uP$|f1NDrAK!o)7~$_UMLJaBSEN97%M%zjmJ!r#Q!{c%NJ?RxF3;aN zst~+nODWPhPHV{C5-@=2SGL9dfGD*;7PMOycc`^%?+f?5plxhm-yrYo#C=>;eCkDf zHouc({TCk^&||Ax%JX*kNjCxGRd@X@7PX3sBEGU(jBjMO>`Qw|jod^OCB5TiPlQnh zo7mbJ;P0x@FLZU-VF2WWEnRMNn(UBNl^mYLDMbPr2Z_X*GI)$mwe1mpRU(EXWw*{* z{qlYfCM-Q&wJ?=^{-Eyb&bL>E6Ql+dZeB_x^nLKFm!F+F^1VlUDBSuq4{Jg8oqf;O zX36gy7@-9L6N%~G$X(Uu#Obikzq!2-Gr#6wL-xCjn3C(yAS>{I1D?Fgc1?7T+O=3PC5roUpJ;d?zH_Mx-QQ73U35DYRs-BSIp5! zKm8r+=Ah^q`9~t+)t6N>nk@oyAH{s)5Sm`Nh)BvTFCbChV(zBtkg-s`n!5``_(w zS@J0opyZN`z4(6}9+wt>X)GhiOb|PdeSE8v#bqV=6Qk0rQ4>S8noby=cDBC0Lr_3` z_@7;GBCvB|Rfnw;5wkGLk(IFMg{@tlqDoNClfzt&PH1mIXOfU(Auf)nSy~HRvN{BsH6eHa?8BA3sc~wA_&Uu|N zvcyHq;X7M)qU_*XAL~EbE&i^4?{NA?c}M=$zzUwjeIh938t(i%g33lQ@gZOY;ykeJ z$0cEo*PC*B%NKM^9?tQH*XwJ^gkhjGu18H z16IHeXNC`nK8$82*p?d%Mw1yRQh(v}TR2eKoNDb@laSd2^kZmLb$WdY;-bDJ}4ToJl&HNh$~gqXY)bWTn#_(S{=J7ICPMz_%|{g-7vr{njKd%t+O z`UeK4Znlf9X40aQsG0%b4>g72Rlge0ss>h18~!g%LWevfjh{3#MhkggN)_vd@-QuJ zd2D#JR5I9Zy7&a8m3fygyTiDsCvUcw-HyLXye@%BY+wmA1}X2K{_gYz)_t@Q=RUR> zce+j^hU>@vJ{qxQ({QEysL4PlO?Zt(?kaia-n7X5Cc6<)ILD9Iq!Y;cJSyXGb2SorV@_ z!>a5J=$|&Az^*c;U)}Ww4mYdWoPdRd-wgUn;$5~>_H>$)lcS_Fk`|RXKPZmevz_mw6(HOk5b7BW#Buam8- z9~v8OJXWRS_8Rr1^kf2FEUxwtQqMjO-t3JzamSZ^q$>5p5wKJBb=&`<%Zakr<9q#n zmAZK&X^H`k_V@{1Y$Cc~2`!T{Y-m)_X2PZqbQEz%`leXnAqjqAJ+LUpPXn}3SzI{` zt-+^xZ|DJKV@bqm%K3nT7k)i@TEfq(qoL}ctOXt(I@Hw@GZG$pcS2f9()!tv=wtIx z^&HEWvXCKDDcO-z-N7+1j3&PPiMt$4Pz z-O}@7M}3Y}(fH*RttiAI{SsO9{SS4BjOl=@X>s0of7Pm2l^_H8VicQ)0%e8qR=w!R zr_eJu<)-hxVJs~yZ)9A;=FCQo^f8#RYQ`qmcZB_}WToFP!3jYl3r#N9am#-v(UDO$N~4E! z`0c-jzsq6U$(!Z(FSgs(DwnO$l>mG#2D|zUKh*Mou5Db=%cjwL_Dcqe!h+(J zn5l?`>fYwrvuK+BW=6A5Z6OAyH8tL}M{ZeuV5w^M;*`#{6%LdAc{wEeqzOn8V7CB4 z(xCp$bq_Od)q&F3%d|&D^=o*)L9Wvf>+JNk!B6x@E$Z=%pgiY&GCKCR)Tq{LBs3t8wE6hbVX9zC@$`S@B05h%CeTd##YVZ2ItpOExP-yNGLO1DIZpHk zi~<@QvQz6v_p>g@L-!kqQ`S)>gSV3mBVF0OLm3|-ldyI-{T8Nh(S6YtLO$il!WI*H zKpHKtadf-gxXiZj(mZZ%(7ap=2zjM;YO0XQUBrTT!gQrLlRCj4hH=`@Iv00d)bVD= z9R2*YZu-r#bfq4s4Dp!9@UJJJoahvnZj%`zdBK44qUs7C=qs+Bj7db&XRa|Bp*miwW-B5aoQn9v zQsY;vJZAa02U=_~lB=CatZva_XyIR)ul5?fqkZo<@1d5`-r8V-Gp1gZ<;yZN8E3*` ze63kkdE4FITIGj@s|9t}^Imodq}xC`mc}Xwl|3^N2d`d*FIEBFrCvz*Fgtz#f zZ2K*W;Ib=1l0m)6wj1UP7zPVTv^5RvP~VSeTkT|Zzq1H}W~h_=6kU+@*V7-lu)pcq zkh7$nJv+_m4-iXPlxaxl;h(>}n@OKm5Pz=m+;-c)GhtsMEU z5aI5uZ=#;Z8t~-M+#%d6I_IHaG=rLk;*APmir4%u-h7AM3n-roQkX_im2&8Ds#pD} z-=^9AIj}}@r9eR#>fX9Fh9Dm%x*L9XF`mFlAFeJ)@GKpY^RX==$^JSSWSuCyy|D1mc^k~~cY^Wq6r;1tbTs&65)UBa8^?YGM-s0VK4l|3Kl7HP zR3aN5oG_OJ3xB^L`?zvFWJoFSaPIh>4jeIVb&QGoRUKM}l?W1xFW{yivRtKTI|N1i zP=Q!E`Cx0|ImpTB=+E#Dg>@Z02g>|KouuQqsz>y`%KP?QJ=%C)9k@?qu^%1aKBxOu zq^|3OPBb`us!XQX@UWJ{m zypoa9<}3Q-Ut8%@cSKGbB)xl>vIiU_#N9kq87l#RY*?8nTOyb*#)!S>ecUWB>KPIc zfFG>j8(YUYOdxoUP<+$odYhm&^)1o|}pg(nz{!6>TPU z)iUrlHW@^VI9LXZYQOKHZlP(5>EUJv21$3RQMwZ2!gI_G@*N@`!gIRqsG{3pV+BdC zImdxj%I?G$C)7g!5Y9WJ5MUyn)xK%W)(#9}@N7=SxRgY~@z0DsgtGFCV*nYCgO9b; z0999yu7+j-#LkmN5rLgYxarK}-ku?H1?#S;#U-R6~%PB$8%^FkqfV zMl~JxN6ZsML82&Tymd&R8`Z3dQ`{L{h@=UEt1LESva+_Vc^@5A+oWL*&-MB6>SEH7}z}U2u@rbO*yXm5x$cbCehRk{EQl4e#V2!_? zPp#(%iO!rFQ(~|F^Ev~?RZr_SEWwKEo57B3@lYL>=fVU0Azm^Z&L^mZ!U?4*KsbHT zt8X5^-lPj+j;ageGm@ASTXCFJ{bC}HkCZZeAC!&H>htMDunBgB20AAbqt)kJeWy(E zNMg~)*5qJi(2aT8hFR(XqE3ppaHkyTZ+a-sps%Z3x@Kr0&XDov} zxTj0aW2kJaRiE>hbxl@x4kUBRhbVDlAkIE>x@~>h4IDkO{8pD{TvnSl*hShqmA1-H zQ6B|tX>7M=kM-JWN!$1n+wSdZ7eF5IjjZPM+=T$mHJKYqHiHY7r+a44F&E9hfqObX zv8()?F{|9Uy%9x03UFk(md=R8V0F&SN+on1xJEnO!ahPiyLhEOY%YA%_k4CW4-Kx{ z+VzHQ-IRL%J4i(GuW~hxzYw0x4Z=8!r7Hcjepe+A6R_GuN4*q*Cry zWUM>g%;vktcCWoJk~PQ<;$gUB#YID*p~_?z7Ei27Ka zI}@@L)sZX>_Ev(o|6QeiGcv*HWN2}6K?Qy1)7yg4Pm6owEXG)(pWmRpXYt?+BCj=i z$;<$b-m8n+ipBL$g|3g+aN4{xDZm(;sF5~5K9{<9YFW7FAYl@^MQm*#+4GI_P)J*4 zypm#9mNzA~r!KpXTK7@>y>V>xE+45&JG3gsm&XepB&YcxR|x!h)clU@Io1$XzXoW( z7g$-!k!=i+l!px!>=Y77JS)`ijjOLug|!@Uly8&C02-ZKJ|Gc0XK|YL;{XGrHTf9M z>MJf9WgmL-L#)~tHrETpeOFR~Z}4fcF_o~DmaI?eGUJ9hp5^-;8Z}eMCl_Kqp;qfd zll`)2_JGD5<@@8o7`*WBL~GpIkvqTvJ-Vc{yig<2(s0R<6Sq#g#u*NRJb?xp>BBXC zp1YD5KUr7y?jo69QNkJ<&oYgCh{*l1`^2HthlvCpZCTMB46~MuF#>_^GXc{LRHAC9 z6MPPp;|+?wm_*BTH@BeJ^>GBT+yrzQM3e8=tu0=fRH4Kcl03@Ld==&^{T3!tn}n>*lK{L{1oD*qm?Hn(I#^Yc8GcKT0-7lNZk*D zk0`uMrBq``j89#r){b&2c8F0@{D4EN8}GwUKUo-ot+E@t&1zM*!zquXjx)aZvSMk2 z$*ul!#&|?iHmh&ZhpFukuU@z_6Xz|_WUj|a@{oDum#a-92O!$wiy`XfNt!3MP|P4< z&MObzI_q62@Vh1Cp;aav>F{^O8)o(78ar!16c^8OIygVK z{K8?6$Wnp{ym83w1HJdm8kD=A%z8Tk-|6T6hb0HYc8jBYKiXVzY0-dt-bUQAy7~r! z6XR4l{5UR|35{>Oub95FQ28-2>6=a`y|At>FM6MSOMeCBuuFu6d?)J6j6K@4H-PrA3&7@_i2B@X^kbQA_ILL^k=gi6Vr^}cs~)!PnWEQD^~ zawG140zRxfq;Ip<1{G*r>qQWE>Qfs_7Dv*rguG4NH#PC(_+%GoQf3YH5X39Qs<|l`JqpZiu@d z$4CL#Zu}azWTX9jVeqKi=jnlr7Zf5e=YRGG);+s(!xD64!4p5B<>@ zd|$|6iL_c|d4=B>8kJ!<0?6p0(c+dC>9I(M&eoIUG=~X3cbFYgz4bGj=LeWPRwMOL znwb-eNsF4N46Lkx@9#18biL5*T3*)0n>lP(!XxaP8m5|q4Hi?sujN*Erp*}(y>xJe zMq907=1P9RV;k*Uj2o$%^qo>tYT6!1JB)}73lk!cGqVaCIh`tRy{Uixg*^hj+!SzT zYt_EP%DUQEy+y^j>LAUP4=!6 z*9ubZ)}L#`7@XUNbLJ{7{qC8dxur=uD*U+#*Z`NG@wDlRNZH|d3_C>(+#+#1Gbq== z6^Mv9riah(^B%ysdeqp#i$8y8R=q~XKogtK_R&`{6HoZZ3(T-#VTJVMy8H$Ta#Goy zj2}Sv_aeeyk!WcP_0uy{iRB3xh2x*$W^Z|xxvE-Pv_Y-5j0|`RRDqJ6eq3YfUtiX* z*iLbfRJ(9F?>Gb; zh;N9wL&pr5gF=S)iSt)Z)KwHW$mxe0=`{k%V>uYw@DgGA^*Ns1e;Jk;G!`p0U*;dd zKb@{WekfmX$e*Q)U^!{=WYk(}@+I!r=U;6)rvpjc{X6Ig=WZin{p-lCxO^6Wi2)FY zL)ggcA>em8*{dm8OINQdj|i$wJMDy(*EXHj?esn+t}MBfpEORD=y)MteatW}@1 zfW9Z1NC;7gFH@1* zgIP~HbKAs~9pAb?jg()1)h7NtmbsAaHyBhu+qqDtT`;_F?wl1771kePdE>owiRE$E zy3t*~5UVd({PQP-0C*#7`S$nX{D((FCIDNQgEUKzs@Msb-AQJaFSrku-j{hm)=HI7bhY1FX;&vMy;TYCj_zM)1~@fzjc$)^~Ojb}O2m zQFmf5#WlZ~s0I36x8+E#!v}l+M{n2XARL2%>mTL`HAh`0{{F(g&%PNWDvY%J22bW6 zPCJgq#bbEm{Gr^}eJfjT+pd^}&(_-=hx&1fy*1;SFH5W?J!5)79P5$V%IVZ{=>e8A z1;&#9mtu8e=Fjtq0#IXdl-bMeMl z_azqn1)&%1_9vS~MMTP6?AtKh+VKHB_j}OS=xZLP%KYcC1-qsL^?b=r(EQ4x*!Fev zerE3VSL=$7cbzZ2#B=s!0s_PvFVDRoFjM$v0}3_;AkJ;f+49Dwd~oIXz9HS`b~`4~ zHTA5#sO(xNT#DBJv0tOgZALu5tS&!YAii>4tnODFjv%|N$Xi5J^@VkGQ$ z1o?M)$TdDY39i*`IZ$Cc_U^l4DKw?OW2CC@KWLSEYEzeJoCS*wo42F>^qZclbAR#k zskbQ}?rRU|i;_e{?{sq3>1kJZ&$ zb7tHQUXN{%MztSUocCgO#s1kL;RdyXo)*%ZJ??wN}b5r2?W0sHA7yBA&1{%L0F6Jy(n`co9cGIr{) z-`2~s!*Yby%wzqTzxR#hXa<)|yT=9BX=Ue2f9`d>dI0`es|3hwjJ0<`EBklHK6wNo zN)J*cct;~<>+%JRcAD+ye!uNMaC71(mURW38D~&-RXcJydb9;gsl2-*(D6U(I70Rj zWRhCY^?rj)%+F?Lx84S`u;GBf9;@v88)kp|#J_#2>N~c8hMJnmL5Za-m>}S^Smg}4-X-AY?uvmnBDoa)yhPjo920CA}ti>hikiQ z^4SXTjlF+%kltEdWgQEH4wz;AVbiyMvPuD;VYIBzS)0Y4h|XBfVG_lv$>y!%gsK8@ zo0%(^Nl;iW?Gya*Wv`tCugM3?)w_pyog;T3f-^sLX_73gsy;I5tJqn4#MX4Y7f19% zPwKs*G-+~lLc9akJ$T`}iP9d((8cT4D=6m&gOfB|U7Da=6cGJGYT(OAI5g6mCxvk| zID34|u*>l<-g*{iUx(yt(Kd&!(3p~#xH)A2ep}i8tdN&qV3{&oUDifm9)&yeC;es@ z#s3MRWePDnxyAjg0JE^N^Z9M9`9z*8y?G|8{`FqxFYWcH;UQJ0^PG+J^p4QV(wJO~ zU_v=2uD6+jzMbSFwaFd>Kt#3p)0lBDOju`gL{QA1%Axuwl?WwuTBF z7uIg?K~R@z3(DRPde&ea`pScgKoqdG#yFpO$D#XitANvvLwywA20gffwpwLl;;EG1 z)xOXFGM&LD8B#%y*>B(14O1;63kOL(9_Iddgvsv4kAErUX)p6-#AMh!Opl@AWH}5; zhkaq=<$=fEV570T_87@F^^4X(x~$u5R{Bv!#DlzijnV zxbrnYc6HLqEyuDhCYbH_6( zP=St1A@)~ZQ&Cpe_GiBR(BM!-OzU8k{LTLEXhvb{Hs%RkMl*~Wx1{n3M@D*~`i<;H1w3Z|OsvlIf^82zvV8)F6*>%_G#cMeSI)I3?f4buU*V*O zlvZjN4VXXMf+H}qg2TYFOP#5`zrl|)3i$fU(!W|jM@eTN4Lp*u69r^c9(mbozyFI^Rf9Z$wV`6_B+ugI?u`6P6cvm)Ya~f7PPsX7_XsIkKE5XZXuak&Y^tj z?+}iu8(Z=1JCLjw$_JfYtu>`(6r$3egl{n!r#~Bb=@WGL4$9lCx=1ryD0F&fVRr)< z%f=dY4W*2!$?Wl%;XVh*dXk$7`CFCa4Am1Dx3g7AO=V4(UNZWhNZ%o)fI8`}#N@A; z%mTqByVi==Po?GSE6EWAvl+b!gunRma%k%)6|8Ts$q-zHlz!vF9tE47lkkBnJ0*n5 zalGxki+%$(iyVs2zzb2|ONi(BY6Du74%uvwSx>CLKZA)mqs)z9EHLTlCJ+AQ%2zZ7 zH(HKk6_)uv2?#|59G0|HUB9(*P9F#2`FQjd?L(=E$9D+w#2j3adh9`CDA}Nk=9jmLo!<`Rp5tRX91IIDXAnn%SxHJ)o%^GX_d>UzahG+Mq;NrTv zPpq7iO-rVGaiFiyZyvTcyG2x^H}r}>PB+eW>d+^EzJ7TFOXL6SgttIF@jZ|q{B{#8 z#@s73ZzWZTfjV-1Agj&0%}5(q`18FZ*jc-+kPl~3xZ3$F^TyN?v_4-J$7pt~#M$0U z%f6_=PMq76@8iT@2d(>v(#`70kYm}hAQ1=6orX*_eqw_Ly7w%|Bc~N0hxx@r749Oj zDV3=XKf74kjh}u#KW;8P7)@w@gWaHdW@%V%cT?b&Zc|A%CRWRQe7g4BiPm;yq%2K$ z>7-x)JB9pVXPyLuhL%pSvIDI8UK?3rMcJ4mXAk`dMmvqey}f;3`*5Q?sTcb&nDrd{ z1~|IRk!*yL^C{5zGP6p#Kh76Ab{hSCG;)s#|1{mKX@hvAnVw}Hrx%hXv}^T@vmb@n z3N=gz=VS7dqzQ0!vRUOp?PjtmeDGgvAzxLiGW7|W{V-yCoyfN9WDjg&n!cM|{Nclfsh72&(tE&;j<1A{&!qJYEvQbn1x zx02{k-6UNf{85M(Lq<=vB&B3f2FQigKW{t-s*c)J!~o>zul%6OVND82<~St{W%UGq-M{c&GOUhlWY-s$hgH%7}__{`EiAp@6G z74Hu;(B*_0%V#xN>@9IiI_~fs10Q<1pUVbtkS-b7TVc^ZD+D?}+Hp4AV-W_1G8yB= zLOjah>}3i1E5GwVOG3kt2$r1q6Wq?zsN}&5FZ1~u`;GNS3Z=YJ2}tL(!zYdb(O@MMC_kU-z@rl z9$wXF$G$RA=ILbNVeO$cRr%Jt_)hU5<5`=UmvXt@H=$(lTxq}ul91ggGT#5z?ZqqO z+$R6LG-+PHH*)#xaA&<;?m{c6!RVaxmy z#OHN~>hXc>kNefn(6xN0aMm^WCo}uDQl~=wPE@76MfpU`ma?`xdQgeXZ+1>HT)}v*8{aPy zEF(-+(k@*J`ypEFAKK^d;cZ2G+2?_X^23u~7$dMb%gZ9qg;4a?CD-BBh-l#oF@>dG;@gm$3kiZ)K;QBFhx zZi42=Tf?gsH9gw!-VeDW``xP$iFk`-)Ak+M@U;cm4@X!u zzFK9|U-DPj@*PDZzn7g>jim$eYDjE4pvDNZY0s-zFHpY2DOW(x2ERCS`t<;<6r2kh z5+-LGp3V#``gIfu3``Sm|C&pI^AX(974Ps4?xA~HkAx?YsD@)Il(e}7)z3i>()u9ge- zK0lA=x9$DDH{TP%?dVBU=J&6RLSBOp+0VkV<+Yc2{*A@dD}yU=Ap`)}=Uy@BS|xgI zkbaNPmW{Z!=rXSzAR0y}z^-LCf`bv83Y8PbWMa>19ScgOaJBr`t+>QElp>NF>Es zSOOOOK;PQe7iZEI-zXwJEb5^s882(3PUt0V&keHpJV&^xdNR4F_eZ!%(1y@k0Xp zq0}^KdT8j8T0W=G-f%=N1&>2-yUy2EsNkjfpInP4wxobda(=)m()7%mmD%vGd1yMO zKIVOX_rGu z_bZ>j(>L7I?U3yCLO^T5C2@KOMa1aiN0zeW9opKxFk9>}LuvKT{BZBu_|8iLbv=$& zfHpbF19_#{8-Z1JY=&)&L;ORnx%cYs$FGVCYV6LyW=9p`BKV5OHSg~g{tF_JFT8)F z9;A_1h8idn1eN}^!*bdEfmgHPOsE0ALpqq$yqmLI2_JRTgt0@+Djt@-Ihc4;e9cR! zoF`F#yLwlg`|iCyV!{Y1i}O^wou6pqeAo8AcJ8Zc2rcAM&q7iPY#x|q+u*p8?O|_u zq*(|Hb!eGoEl-;-=n2O4`^BhIrpb?Ed~$BJTxq%)hgF_V;=J{@Q>4+y)ZugaM8<%V zwrG=Nae^L_9$mAeZm9jWlInMyO$7t#n5`g2|H7)bjBScl5L=+XHN;MUC2_D8=`Z_ui_^_(; zX)5de?|hXLmkNK&je3KL1W&n@Gc8j4KNS9l#>XDi#-w8JVI+Hm#SR;4{m9fjn*%a_ z(!4upBv>|iK$q+|J2!<4Bo^`WxF!V>!QkK#iSM%Y)c}4D`##?F%)&S zL@#sNd04;*?L)lV!F-s+ughtI^=A%zY`VM9&RP;77TEO%rCP9heko6Vz7wG1VNYQ# z)JeF#*BDA~ZD$GSDj4Ruz>hQ?+S?;rl<^Z#8a#e;2kU2nLv3u`PEh5VjqN)s4HN>s zTUnYhDME;-6Y-hdnB3~qAF?)PWzkIO&<%zAbFE2)hXbc+D$h957^F`(zT1nHm}Zz0 zyK}dkTBUhWP{rZAS@l_9Rn1*W2P$$&$%*tgL_VzOOZuz!il4(A#4|A8%jwmdBKs#* ziPL0+a>R#d@QtUsXJuw6f56I_PZG-WA_ zTfxRjMhRu5R2h>Nt$Scfhf}H;(y`;zTu6+^fB~g8ggmY(?+xXEs(eH;%SVxs4YWea z%aLINR?02BFCD*4D#tZ@!TsJfKn}KX#BRl~4u3kqs&^sJkJfH)ES4U$iIz8xg+0bn z4y=pniuz0yln-=3o0B!Pt^wX=BqAiv5{Ny*x$N?2N#9Sii)D)3AXCAv3x>5~jr3i) zFDcrWcMY_36%dU=`N)9+GKGFWV>y|3j5C?#%wvoWhh@P(NN$28_y^I*<)PK&4~se8 zWwuC3u#;tVP^;7FGUCvDmT@x{tFu*IA?_=RAzIaYf|EvR=@@CMzsuTL>Dk{jJF|xm zQxIZ0LCW78%3|8_T%A<{Bd~i@K5K?F!l;JQ;xSdxb0nfQLK8M_)D=8;zz|s%xn2H7 zQb*~2khNkqKQe$Aln^yX6y8lPm4+@nBTdWO=r*g~Ox6|88fb^`Pj6xryQVV8j`q7P zr5;)bePq&~HV|C8@3-7jmjS^wcv*OVd#j^Of{J-n^(Z8|^qb8?c43s>21;S(-gxWw zz2s3Fzv5C7e7$^aVcj5>1vjN!5i@sGop<)L2VRevVlq-v%o0dLwSYLi56s-)Nsum# z1;Qg3?^-2d#sdsk#7@9e+Hte9%S^x6*TIUB?`xo0g-qfmOTZWr*Q_eVL46v@%jreb z+Oc?%GPT1*C9Cr+q&?-q+QL7jG)K`8U@RJmFFM(ve}pQ9aKbyEMrWyou3hH~O@!i=Vb=~ushO~29iGQDb?nVFR9YhQiH*hP2vjK0N7{8-6Y$x19<4Jp4(ui<9z zpH50t7SYS5hShyxe~w8=^o3C@&D<>ykJxKq?X)&H3TYpTG;}K>v`cuf<2}I0a-q^X zl`}+00z~03&!ugu&f{SUEa%p8)i5H|7ptl2MmMWDELb>Dxx^S)AN^&Bbl0V(l%a-+ zSS)|2!u98bu~q%;Vf_`D%Z<#eq`L0?f>rThnPytLg50B~S@U0qK_6g@9bR$f;G8H< z%}}F^2u{*X-npP6iHd}OPHY9t!t4T;dF$w3BscOfHnT{x;{7O$jO5u=1?xj}fc5YT zHZq07E{pXd1GE1RR%PlRP0;n^R}E|>hFY$`V7mcxi7?od|6qM)VfO!Ezy5=5lD!|J zF>}9fs)NDW>51qFHro6L+bILH{|6iU57w;WSC>2t){!`plQg$!t^fv`{vWKzEX@8N ztmHq~KhnFyR4{Lw|G;2vj6`$=8_WNL^_PL!|ASTf2dic)8N&jD_0WUC{^tABPMy!LWlieFNk>1Ele6HKP&qC(cf~$f79d~D_!qqmGtj#ab{Yu zNN8g&9pf^ujL8&*HR#hXFMmxzuS)+n`Gj{?H2*)s0RI*V@MoK_+Tii<5gZJ(wkp~R z2Fmmg^dd$Y7Ha5$1aaHqt8ucA#vC?t=}h#((PuO8(FO zzaj4V=gFrd*(KJ0v^ZMMEkD>O1DYK9|3&b>g_HMc2DATf@;H9OCJ&v?fDuUhj|V16 z*FH7$%i~6|#z5Vd#B_QFGs{XMX$ke-X3JG@ley+sNiAV%m57MiTACAV_>;e%xXGJx znwm{)^4Y|$6d4Gf5}L;ZGg6}!_rr?`we=mi^=hdgu-8@Gcq@%j(?7%Ko67gIk%GR3 zhs6~5%aqOU{Y+iLOU|7?z1>(Gf9)HA-BN7T4D3ZIDU)$%6>L#8U(F{dEk#L}xJXM+ zneN|}3mKo((m_+=sB`5fUSl)r4f;XLOSD#1_cbQ=OD1DPsLiaWFN3?MQ~q>tii{1# z^w2RsPMutslq?Cf({$KEbErCMaZd}lW%4^AofZ>^G?=WPjfd_1#Vn$LK-osJKzsir zLTa-)AYN2+=5JIbJeTSbpKX!<&_-6aZjz7)|2Jq%K-X4!uVET51~~K{)Q83QpVV4{ zc;d2+URx|l=t(yvzXv$jYz)x#(;$^PlZBKBenOBG2dCoY4Uc-U+0(5!>`dklfT@|y zh)9ghgF&v(H2P1jTZfZ(&L{PRo}7zK+^alnCoRVHSB zgIp07R;ual>3GNdkwx*Gq={);mVCY+#^GEsSA_Ini19|!`iN;TvTV~CO-$>=Iz9c3 zg0W`^LSh3BM==mjVurO4_0dRBFX8cX8JtigcrnQ+T4RVQ-pKtpn0tEgIMDjyw>Wsx z6!1~=THY>eZOeroFjuGNSBWgcM0FS3=;%UHe0|+OtO$PmdpAGLVfv?{<$Z>drWsn@ ziV{>=E}mDjVO#i*<-5V#+Q6k7rRW7^ZlCcQ7Zbj^6_@E4o+0KkjCpRK94sYi7(dVY zfoCj?hbqmi%^ao!M`y=XLQ#CcNIRJ9L2`!kaGt<3rl%t{&r0NbG#6fFKv(Zwcf;Id z$!j|NSWY|Cv{oAcL_SMCF{xXeJ&Kq%z4zTR3y$U)>XDweR=j0k-?mf;jD8u|zt%js zhiMEw9Os1M5;&R{)uUnyo12^4+M?Tw#8tU)T=ntH>_$)@47V8#XqS)jx-|OPvX|lq;?i!uHBks1C2coU zZ92qv-nR&+rZV}b7# zQkcPt-l4V)%DA_-#$XK1QR{lTR>l+Fmvk4*9I`3tKt8w&A3}gr;}9Kt721*IYM`FI zyFAj?+-VkHU;==?QZWb6HOR%tC>}Gex0<8-I>Rp4sXDrFS=lnEJ2h0RT5R?$O|Bj6 zOOweZ$8iT^aCZ80f%QBXY^_;aX&Pzkh`$Q!3{+Z1|8(EhPA*C*-Z6k3T(`%^UN5M$%{9wsmNN5RFqXt7#Qxi}YW`yMMMGpnv`U2A9{sD-0Afy$ zB}NXPO0-PlL`2~j`{(U}1F8sp{(IU9nAiYVuc@cb6@xJ_dAX{JtiYbvl#g&8M6J;k z*);t_p{8vW0)91&ilCO!op9`rOey?M19F`Aos^f1cNrh)Oj)$I006fb8ST~n+z0c+ zPF&i9n7|9mo}`4rOAY9q-s6P-`)r>0Lh^S4h5Q@Bf;g!d7hUqniw4?Wg}5iN$!50x z!A9ZMK`m*#T$7`+y@iI1ztYp^M0d{-z+P2dno_!Pf5ahMUI?SfY3>~k@AOVvIK*55Mw zH2s4nU7;oTaSe@{Yi=w!GHGv5MmEeqpeeJILMukCfmk3+@dDf7SJjx4{K)mC{tG-=yCh`&hmZWEo3r zgO9#u^6BYdb|4He=v|YQcj+vpnYt(Q9gRlDxj^yWpcN~ov(-Ew>%r7~=Bz(1dH?3O zO*bW2X$ZY^X7$+-QTMAWoH0Ks`q7WFPo1_XMX&uYU5q#Ga5Spvm%GB`^R7-V-{10V z0oHewpZQ4I757W7K1+m@k&i{aTFcIB-(TzDG=jIQ<rGDf|uOLomP@Kc)qxVl11Es2|p^~SS zTk-j&gHWnhd$YE!mkW_@{|=wX@^i*Iz4orsnOKXiV^>6rZE@3uP?i_FWIr1T(e))f zQQ0k?KCbgOb#gRfmwn~{-!|ZzS6Tp@;{nu@2N1+*=ai^)Gt+-ISA~$}K+u8fbdjqr z{D~4}&Ax8*9Tc)gf05%SW$*87TSeCJRJ8N6C#}%<35|l?PO$9$Pl`1wZ61L>H;>^h$g=5ZFj5-5yt;RzuQt1o%`Ztg^999Fw1RfnI-VClL+yja3!9R@5dzi`eebq zX%u6+R9Z4}*>KV)O*v$dK<8bpheT@;4u*-J(1AJa#yx(`0|XgRhl?bJzF09mOC6oB z;^;L*ldM4OdGJpoI-SLMjGtO;0&#DKUhK|EP>+fmJ4Nv0BS<7(I#Mi^tgQRBN_csg zT;y7$#8qjW?)4%E8~Cv!QCXggBC?63@4yE|NXrVP_25=X3mRi!#hY2$3n|;+xh`{{ z%y3Ym2Sp+_nDMa)LH&QPJqcX}Q9&9-%UT@Vc;6SUbXRkY$3Ul}6Hu$<>9fURanmz! zPwMYPVNasko(xsBIKZ0tVsVs~+R#93*PDZEXpg^L_gM2pcEok)nd0alWNK7 zhpXh|wtEOWL)YeH(gL2B*B6UN4~RHw2cde6ZpmVukY+7T*Km>kR!%FE*X^I$bW$W< z;OC@(iE}sA_daF$h8u3`j6s16&3`zjt1MP|^7t&`_f9^rw)pI>P%rBK<@NY8KZU&PBmq zEjDdIkU#mkGjY*?gDSFeR`l7_I{OmkOy0{N{;u+jcc5BJm83I|pqU`dfbl^bQRqVM zJlgU`J@#QOq97!Vlw&DDl?;tH(>N)|r7!tw>jRS~a)k2v-GTBSU-wUBuDWS|&=DW{ z9MtixSFXR=^rXS~dt|ya=1Kjck3Wl+Y4O|6oWJ_X=&`g@Ayo0=+LZ`(4w-n&vrQoglqg1lqT|C37^VaMbL}noD!bE?!n6oE-Eh0}7k~Th zcC`f0GaPTXD&TtRN_nf0o?h9+^kqA{$i_+IV&vWznHYt`wH9g<)2<0K;fQ5`E`&Nz=cBUV>?cu~|Q;keR(uEau4>M&L`RIlhQ<1*qN&XY)2G63WSL zar2z6pt?p(zfXG2G?Ym_$>UqBmKNoX;4mvW{<$kL@VJ=j95;}!kCvPWbYS#q^| zNS5Ux)?0twggG0Ub#d>;YHrJ=T=G)V3P9)E?Vw$haal^{0upRR$Wf+0=f7JS&4WnhNu-M)^x%@PkreupnrXqOnqblUZEs$ z+GOx6GR}ghMpIlmis1o~u#9~zEp#_0hbCvQIx(4V#VOAGuQ-<4;^?uc3#vnX+cA%h zr99o;FQMcdzOL?8bi{!2KeYHHr2U=N%q)GOk4DnNmU>fl$pV^Q`%`4a8|6WU;@QgV zs;qOYiw9m7W!^X5{TM{K;)IuW;jRbk#$vowV_mj(9_L*X^rIIa7=sKxl5P_Q3!9m{ zg{YR!)W(~tk(WdpU8h%*rNofSABDqRV|TMtR_eQHFcmVO5tpF}m?G=x2z1_kA?=FF zTo6O(N?(&^-__63DyEvO6*u^PcPQAzIke*cyy}14JPL05`);dR z7LcyZQJs#NryJF@5vVw`D=iZV~HPuH{2$*f});?|oB2U4+XTgv|o!lw)A4QT4XJBSWg z6bU34D@%)OH#=pe+PqsxVBuK-lz(!125w5Z!Q=VW~5uxx%nI97fADzX@yBJht`b7z=*U+BR^~Q zcdgF!CvLMCypM7|712>cUslqAMRHTl*YFC}y$6OWdY?SXKBJMH3A+&nPj$(P4%69e z=n?CKretUd9@g+g?rc(Z3p22{(t@dMloYLJA?o_a#eD7;9<*96R1t=r3Iow+rq0LY zkczS|TMg{YwqB2L%`-oT9c~tOB@RBE?=yE%h;7J51U#3RBZ}8=R#ag(s9xQVN(|?I z#S^3dc$1RA=-o^0?gF)5`f{l=AA6H&VE)qnOfgXK@(T7t^i?1F^P&Pl!;8a|q5%0o zI?Uwj>Y{guQWwhrnfxO@M_yhdt}pCRfdOYzp|j1OjKv<9q>}9Keql;|&0Sx4^=%Lu zo3pvI(OPyPm;xyQJs?>^uKO8jT$YoWX%`LWR@~42+!AH2P(1b-?C+(#}XgiF4!JH)GTfA z4C#x3TIo5&vA(A9!dH^xlpK>(K|5(tUBj%DR8&y-?iatt*?a~hGDurkqm@Jfu|(DM z3t=r+I=L+v2LlU#5-*@VeYEM+gL=>KXqo`gPbCo;!W_s_>}g8z4JFD) zA@=I^-dQS(_2;1BlH7=a+9(5K)qJJ17}JrcK(-fKSuN+vbH`RSQIj_(DLmZD^-k$) zsD??}11U!=aHr=NIXh|tWiB;m6Ih{(+E-_%8(v_<@sl1aLC3e2++y3jsd%fOe&GYq2Emt!^$% zb%{ew36mql45fIMQu}+SH?!B!dgx-{V-2}O4OG~|{0h-}lF;a6`5es2k9U4$N%w}J z)noNuH9J-Mf(zulUN3#cfuEQ(z#`qD;P0x73=Z_ZVNvvAA%u6))dDCnlv zVzElOfF8cK-|6cD1OR8BnCyXWk-X;=)-KJ2 zZJ`9%#L>RuX)#{Lc}Gs6dpSgCT^7&W7ug9?Vgi^XYb}cHoh~npUBcN0hzG?(-^Jq z%dkWFVt>zD3QyP%e4we_ISOeW_x%`1?<1URuYScwJw;f!ZvDXB&KbRv4ktu+&VAu+ zvuUkUfCE78cHaNJj{aTD(NXpOJ|1@!h?rcxA?q$$ivhSPAya;rQRNj1i6PtX#~<-cB-JBu5UDG=_lx^=l*br_|HMq9*U#Ovg9 zI(!N!2QBfi1n^gX)U4j~t^H7J9<8{tG#$J%4%xAvZL7~|=0jSEN0X1pP4;cmuO?{1 zaN~uXmko^faj(Y`hWdCWhS1A=u%$)E=jhFEov-cI3|(X=X?ZK=sH$tnpf|E}#85-K z=>E{er{m0EPeKIu@bebA8J9YKEBR##$Q?_b=UzdnwLVBimc1Vlh15C{PrK8EJ(R3y=Yz!&Z}4Pz7YNo}e7vh=bN8R6n&5K?o&ZlWQf*ASCEG}0 z(Z%QA3I+`Kc?euHy%#b9PaN?-jKMZ!4txfx?Sr!Yjp3$K=d$c1HZ$|`e3}c&OEDt2 zNrR^r#v4u2SHXT5PcyesC`D*X2qBB_r$9e{1YF`Z!Ly&|V^~OsDibAR)5h3M-fjC& z$C%RfWm$9`?bt01ec@jW?kAoN^vL-=!=(robxvtExyEZV)TCy4GFx(NL(~iHkV3it zE~h=}yj)a+smQzL)EVbJ5DFUp4mteLRLj}ONjg~p{(_hGWPjms=;L&E9vkyrD=^P+ z3r9%#$7b=SEsa--*E#pAiJAOj^3^uC5%rR0d$0k&*#SA0^q&5!Uo_M0?_K%dt*7@y zuj{)6Ll{9#m+HV8&~2t3E-}$?^*fNEiGgV-f@Y`m3R{{!+;=kJi7t5pHla@G`u36% zuc0|OHaZpI56cd05=OmKD~n6LqkmyvdI;;W+nS&UOMz|SPq-QE7L99a(ZNK_<-@6! zLD&o^fIh?#Ju)adu>(`XXizcdwbk~ieC4GRK2KdT&unHWK>FbNByXQH(frU2-q1I1 zzZaGX!vJ)v?2^300ZNCjy{RRrt5LD29%166oD&+omRyGxW;V{VzdB%vG~mQbV&~2;=+i ziip@h7)NV~to>VLp%`q~{NZ(H=@++5?(k7ehTI>2<0^eA8;OC}G9e6f13(T5vF!f4 z%5OR03ADS2c*g7;&KR>uPqFoR*0Il_-}!?KoZYh)>86Mm1E*BF zYI_px1xx5}QoRkHCBScAX3{ebRU@7;?ErdeUs`CJ3-VJdoNG^rHg?J`@(I+s(j#y7 zaSMN=^nK}NL%e?h2B9m*l*7(Ei*BR`;+ z7bo*f2P~|s%%)f=#vGKZ#uweSlF(7v_ewy|*NeE2ILi)dRJyVzA3~>p?73S>oDKD} zkbr{*b3#Vk4AStANxwExY7JHqXi??7Nhcz8@i0!^^nz7`?4m2=8_bE+GkgZ{H$Fl=|mabESS)JM|unBIh zY|!zRU51>lo}%|a`b(rpU97jiYQ*t ztjx=$Sjg7(tQzkZx*fTtagX6tPyrGN*^_zS9!s#loD(eTv2~=hd+;v6rAk-hq_Hrk zbt)>CkS&#agl%fQ-U&vNh>euB=%S$Vp~Q{L*=`x!=oiz^cxbfGU$kE*8mZ*G4XShm z?l!aBsbe(j_>2>4rlE&L=7l71_LlwBMMgy})S0oI5!-k~gucveSd)VW{| zksa}-N7RPQ|MCU^QGup75KeY`i{TLhxNUi4osL#;V-5RnU>rKBz~|SS(W%n*?a8Dj zU@ftT3yT7w&p}RiuTKB4!r~ zEvAi5E;VMAd4vJAy58#FwFzcKIe4M~va3BTJm_>F?rC;IeI<|#Ihi|%JysC&u>tEz zNYUI)Qa2m3G{uxGI=u^+fNU`Ik2;T!+5EKt_xHcbmseLMh$+I7QUg-ggDW+WpEf+t z5H1f2Ot|h&t#2p$of8>YSTft(PZhHS0r}Ax3p49*Y24=9>O=y*+`+XJOUbQ4O{c4} z5{MW(PcxFwQ^FS{bMtf4E08Y6?TQ^mz}dLhHE48iVte5Uq;0*uk#n{E?Ggop0C7!u zCTmgwLr8mFsKl*!wSCpX&Kpl?Jsf`53oLIwr)`~ni+}->$}bSmzrDCX;%=d6zq4;l@lO1+JJPF=|OC4Z9haDj?B<^M@Y=78?oP--*E#t zJ8n%wn*>e9CCkgpu!H8WmJSYsV%Kr@jH3;FLp{C0Tl9<^lq%!0Xyk$k0Ps4SlMM>M z6U#+re}J;PYcr542wIllLF<&l`FLAm)bZFBHx*qOM!uBuP0W8w0Ba(G)rXOWJIZ0b zMOuk9jrpwOvYD^U<27*m5gHBbzYFk#ATXIqbd#@p=47`ylC8C*%V5RFU-t~IU5`P! zzNW1-hdEXGsh>L#<$l0mhmS->#$5d4OhV=P5>V4I5Rr{UFkYIHlJvKJ31^^Tqw@v5 z=Xvdksxt!^o02t?`^$N4&>CxyYh|dsbq^kqKY)-l;)AcZN zM`~IUgVI^t8Ncaz_ATM2{U?Tw4YSB4c;PT`CFFubyU}-z~8B%wq6z@))+fqsW%{KO6_-l z@#G6lgH=K+#IV}TkeP#ZWlvX=E#$MLnjJUt@p60_aB~$&6RpoGAFf9%$w3*Amq=Mz zxdG;bmKo{xuzh`>RLByAJBOPv^CE%;9*C0PovxcQAoG#<3C z_-*9+VieEqMmKwN5+6WYM4K-xs~yPi02{X&lnwULmmE{8%5+F0;`M?}IlNf0c`3Ykp^D_!PY5^S3 zL-{sMO}+mT3bO2C%VzZhGnQACIcvuc%GFmOeIJ-k>_0isR^Q<8Zlpbh2wxGd3HdXK zJaxY-KM1@o=E>2`f4EgM%71-+b_hpd_=~q^e%&2)(P%p+{m#OkR_DJkN;3>f zJQvV|tUnA@&QZ6;pxWE09dWE$bz@ZnY~HVoiy?KI;YI9P=Kh(`d~L^@rEX z5OcmmAPPj%6Xu?4pdy;h>@aPTL_GiSfksIC0mha!&-P|d6wB}ZH-E|@ z9PJ-havJ-c6nl%=^O)>4m-u1f8`JcC^tHq824;kSzmO#UUjAu;Sm<;eGCa64Sy=?^ zfWJF4@iSvKw5oW1mlaI&nop+9wKup?d;BxbA1BuPi*lyQivo-1JGB0ggSyM>UI_3Ge+ zYC0~odZXfK^cKH(efBcOM{?KD8&i&exdI{-zB4@IY zB5br=Ddv@^_urFg^jtFAhJL(baS>!I``x2@_^rI}cQe|e&eaF<%MIw22uH0aH1^dN zy0{O40-Mxm?>uta@9-6V^Qw(ws$bY;G|=ERcw7F_wZy4Mj`c?DeRCL-REzw5r-b=r zEhFH88oJk^RO-6ma9w2CIm*SzcW85FPF%L!g&?K7Kj6=>AR2!(Qg5pS7JG#nF>td) zWgB?MN`P9**C{!SwaPer1^Rzvy!$9w;+Q5Rj|a3VzmYX`Xn@~@JeG95WIyX{c|OZ2 zO>-%w7vpCj)L)p*EhlsPgMV4fW6M|2)^!=#+382}JyXkz&PU@-qLkvt3pm3aJp_j| zDwk%3^@2hlH}5OO1v$ZG@+uFIL%d#MH$ zV8=DD{Tjou_QCFc1KVxw6$(1lvm76_&qdo~ryIlx0YFG-i)Vd)EDO%}q9k6f0_4^8 z!?_I2+$a{GMsAq%pxFart!gwhw{NyGUUMk`OFo<7mcQK3C%Rs~iMkvLP5Y7nyo8E7 zNM(naFsD(>Jc4hTX1iX-#H7YI0$#>685ds}qZ3^4F`*`U44jMKubHks0XRi;0F&ou z<=&}dCymI9!?%Z8or=_h3WXYD8}1=!$QI*4ON~rd+p}Y*A6RB=YkWo|ltkr%ywH|B zvi*`sNW)rTgAG$2ow1)M1KZn*+?u*>LEjq+6Lb$bKOy|tXeGgyVwI{jZ>z2SvF?3` zraM_pZrzTMm}2}kh&hbMpe0G+w_*sSvhdfvr_z+z4UgB@C8?F+I32++9kP}O z@nV9%c||`VcGGA5=cherWVPv z+=tFaZPnbt^~mir6id)zLP38u4bFQs!3q31>K%cEmQO*ST<6-p>2=W;tHRB$A3U|^ zgxJ&*ghw0^cTE`J*WIeK(mh z`c!z8_E1Y>Jr@VOj*)}*P?jdhkDpyMI{L_V;wb~+AI#uw&SXsBW5$JNjiv~+zKoE1 z)f@?In%`~-!juvZZFmL^c}5XY!q`HC`R7hqm4bad_79)UFjB)m}B`7 zq^%RWrzE%HVPC*5a$g#DGPm$j=||&RmgBk0h=MWSZUu+ve$3#%ylkOBJUO>;v!Jjt zig0oJ^9CU}<;jM&fy!VO zIA5v(u6*Aeri1}BXzxZ48ZpMYc$_#%r-h&b7(C)i>TCyVW1%goy43uN(de|9bcYNb zw@({kr-SQIU!gIze6x1C{lDvLjmAfuz2fY{)c4xetM0!QNdzQcAEGUPDee2GOtHUi z2OJ8CJ>fwweb}c(U(mcEIROPm=Sf$VG^&04X2SEtZI_rRio(M|?tA5@sC}Lte{-4+MSnY1VddVLVaG}Gr zn>@NF#I=Hzc&e2*Zk&hnoPUFBy5cAV;5#CoQoSF}OS&tn@pfD0cXJAmeO&S=JM2qV zuWxHL-6A}A%o7eYcs9dOnT;B8gW8JawpC2(q+Pcz1Vl*aCA{9tF&T6vJ9J&;`ET+H z2P3eB@YRJ*HU1%%AkJ@ikOlX8;reX6YkTN3Qo9M~;_YkMp^K?g@;gydue-y{m0}BC)2EaDflpNSqn%uy9^4t{e8CW( zmp>ITou*tQxam)1UO#IokcE)>C0+$+vj#m*J}0gVZh;u@K)ximMh7>AeCS4b7_mNz zlYZ^?VjUO9oVr*qg4PTY8d(aDjV!`^> zKm$@6dq=+U7+3rG_WL|m$-Oo&LogX>y6jYC>u1E8<_^?M*(A6f!Y9_)>k`sb2T+ZN zzSHduZ>WcPM@Cls*A1dn>5UDSw0R@$iHN2&4uW9L2f~ZLQP~bE#gy%iX#|}u zZ)7r5ex1knyZa*Nl|b2TSr>0|Q-ofYzt5FrkoB+?Ao5&_?qsVMz7;n()39NwDa2IV zApNCChO)Q%jdIoEb3Ixs7pwhhbdkQHR`nAvphJFfE_)^?`S1Qyq`zN&_xI?t975Rk zba@Fuuj%~dYK6JfXA0FM8K4<2?dyj_ee`_F6w5UI^-l5&US}z}nar+$IZS<;eBm-v zOMDM_NxX=sG3TQmFTG3J=a#fwD0C!}3NAp4QT1s)p;%EB9x;Z~%5l=3LQL#vvU9gd2fBf$kUS=CzNDjL77C z;v}n-ex+zADR^f7!aLZ5_QfHr(+Y-*@%fMUj1lR4SwDduMa!AD9$~*!F4H%q2e3UST}k@oG(G11gUu=9KBQq6Fv$)=|9$jpx6vqW9(0w!7>RR8Xa&A z-E5(V@Y#TgdPH1X-NB;rAQ{F_9NFSNLajs9Z|iYIO~W?xkCjujTHXQ{L6oNb$~A=> z*^+$|!af*6YH3ox$7)5qF0O&^gbY7uy8jirsb1FKpbp76?kc(3$i5bTy4@d0tczL$ z-AY~s`+CSYFuF&f=Dj5~F}V@ma!P4pnBH`=Tj1|GuV!v))^zu902ngj2?<6)EVldB zjwt;}@|`1_CG6Rw%@znTFE`Pat=Sr!_c^b*F)U*&K_<6XsY3buiK4ZAY-~cUr=CH8 zw6XETMc76J?F?5zyB(?33>TJLHfTNg(amVO%22Sm_lX?KOj=c<$G%+DyLZb&|6?uZ zRW6M4U5R1J2Pj18jz|m%~n};f3XUKs;<0)44{`?DWQo1Q$CW!18+Myh!NSn&QewLxL z+8eTHjXCwnqtL<<6|1gw>7ZdYu^t08M!VrR<;!CgqmF`InBxxpilU{9?UC8MU(KLL`t@)C; znE$dQFy%m9W)9EBxx$|Mm$0cC5Xp0@oa^T>dt$=KG0RMupvehXcT#)9hUa@cF?!Wp zhVf*NVWIWWD36S^*IM0%#ok2fF+J2H4rF@$(JYk&Zxs0t1a)`2_oo#;yXd%Snro(c zjLPueQSQ%7dd*1z|aH5N>?6Tx2Y zv1)1Os+)%b#V+{vgL1+YOPsD9cZ}fu^j*1!gJRwtse51EqrDOouNng}-PM{s{|)Jh z+1^e%L#<4XtlmRb(bSBYJ6bUQ4Uawbkvmv1V^r~Sr;5{!ZcVCwJA}gNA7Ufi5 zJ@#N|gvQoojC?b34oaRo#GUkSGHI_e8kk>G_hDS90M8o^=S7^R;flIZykcB3vB%Jm zEi-3P(MRPS_YHOI>(V^fF|3zs zEgTtI%IQxCO=WoB#{LMsNzn1RmxohH-K3es74N>F9p31 zBm+xbOMRs2!HoXoW2rO2$&Ddnt6#r+niCl(pZkyO4*5{C8CcPPbmYq?G-RRfLDrUxa7_&ljM%`!5t}CC=0el1TtFdU` z@P=GbD3qV1kN>9yfc8kaH)<;rr2=XEgdiz7^rG(57q;N;wZOnogd-J2E`>Hw6td>R z9Ejob1EJ-R98wF}^OE?yr0f*=Oic|BP=F;7_U)P|@9Qn{`=~KKFgsmzX~8~lU~)2K z_z>-v7|s#-`%}boUvF7NvmY1L09(3JJkF5v(&&wPr!|mLyT8eI(MZH}+p2EBc#qQT zEDnpuufA4R%Wg%Yh`fpy9?&`7FD2SopeO%90Rjqi(3-nU(Hp$5p52bpgqtoY7IA{f|#P*T62m& z!Wb39;A-;c@O;F{Ru7|HvSML#EWuxTOPFfMD)rPB#87kQbHhg=JB}9m1#NsIq~7QC zZ@o+%>6YCu)^TrZchNRP^W=j->1qcIrkC35CMGl*S^RtM1jy%wwDLGI4IAcrF&*m9 zlQPRPhF#U7EriBWD+EApf z4}$nm5rm4NC4ktip0C1L2OR-5DVoF)T{__G^ZQ!1UfR7bz~0A*=Dv2LyIMu8cfjXM zqPD|+&~5hcFd;zVVQ`UKu+M>!-G1^yG6B8BIl}80i&&P+lR|66CntxI!Qc`h46UJh?kznRHZ~PE>A*!h?JKX&s{pR#)6|!luTAoixSOQhlP4 zU!E}fEq90lt1N`O$+=zjzax{614WEKshXljiZ6EiCtXKdmga>CtrG8?|ZLyhY+uF*FRqTNIR}f#O3wom%21AJUd2< z&9N#Qkn@RNlNRi}*8qG$D(KyK&fzapf6_N-CJkh2a@%tI<8?%8rCh?W>{Ilc$%c5D zEDvR)zZzkKaK#`q`)+%Xq}&Xp7W83setVtNisp%i+ycc`CD{YoN-Mh9u_IRe{d|kT z9UeT2s#=^N=1XoK?9GO;TPugc^2DcI#{16vyoW(7Ev1J{+5fs1_CW9E&W@?Qvk_1@ z!B?2^8B}xlhPQ`LEYWq`f5PrBTtxuPg`Xbtb@SxS@wJw`{Afh67g5z%XH2Qsaa;iEN2{-VO+2u7q1dQ{`l(F((xb?Wo2s;$8->0U}g2|`rVJP9oX8#aS`D&G4fd**74)Xi^Yy9}m}YQIJ-!ZIr} zHVU_@41k2bIj;rOWtV@!3Ve+--{wDS%Ql|s;&52U)SNFGP8JB*4{gx=sLAM$a#eH2 znhNbWO3_pRX9TDgX6xxP#ii#k;T&77?0=44J6+;0}1hbmAJuDeO2ZVB1g1RAvMiz>Eq!C#ag%=1i6CkF8zB)jg_o0{Nhu@e+XGg{Q}usaBO^qBqaf6E^vth?{5@3%8j-| zm5tMguw}aTG7`}6qhpb>w?;~mtsV8pGzXL$-6ZnSEsJXYoWg4%gUvu;J`y`OMtCoj z_^;Ow9du2#4<(gj;I9H|A&AsV{IH|t&B+plxp3u-xwF8>6H!ceG7ipdPwTCS9wZB@ zt)UB+B8-p(8m&TD(}JV>+`kO&q#T^?HR)zukB;}b;bALT=bU`SWKph;2P&Um2N6Fv zSrjDZ-&k*`)P*$`cSf>=Rp1m4PLI4gS2Kh;s(N~9v@HP$qqy(;c{mM>Pl(ay)6-_e z>BA0Cm^zAaBpAY|NJ%in*z86r9kOwbti1m3qc}(XqMuTP+^h_MXHBC0x1LnnsxmFk zzyn7RIIfCw>3^kG>O+&jyAJX;#oz?vyX}J)1DY?0z#gLnT)p=-xBMU?!k&h9;99?e z$=>6PuNXX&fOhrjSb^TT8Db#%*!gbR9ab3kV)w7Lh6c~40rH_qw?0647Xzc|w*LHdTDeD!V$s^w~@E8F%SGoB& zyT+}M4P%A=b*k^_oBLz#%M+s7-BCRmh%_WGj!lRTfl|5}ED0)K@Zo-AGd1NKFZxj# zrdG;v=U-3}&g$d&OwdjzB>a;}wQ_N$nP+br(;fa2USMLuh~yBu$dQ&!_hvA7vZw=% z5YNU`oW}P{Y~5B(<8_||&~}-(UG@hbX>D6)kc!U|z>|zE{Ux5^2xpY5T68T{vD|a} z2ix?QDwj*q+@+71Eb~`8!#5SgIiG+#pEXdoAZFeG)wYKUfnK6a>vJI|+Mo%1xHIjE0yw&X?TnNkT>Mb-AAPc4QmwWKa4#wZaqUsum9EMOlNPQ7#p46w-OsM`%P^ z+Byx-o-;ah1UH4Bp(W%6R`=`SDcYzKDmhMm2JI_$E{>DUXeeuGPQ{WHF1mKh3UeDF7MylSPAJ|HzavZ>UT<$%q2M6#<6 zJ*Bmf4q=-1?6v1JBR>m_9r`92^a#ySR=nTS^Y$DPwUK|YvEpk_BqWo3-csh4RqyuY zg_jYzgYxG4wC~pJE>!dc^;EX-+hb!?B5{^_0&uEO&PANq!yaz@oP3E`4(HS#OFL_} zu}C}4i9;|VIrKrCH=|Z^vWCgS3Y2RsHKo`gf*x2ZhapDqA+VZDb$Uz{Po11di7m#K z8E36U^K2YS%!j9&98oNX>5%RrCY@6wAhUGaFWuzura^UH$Xt=P6e9 zGL)q*oCCr)NMYzlZ2AOIqI!xTd%i*9p^Tw5t_Y7)_FT?~WpBpk10Q=x*}4M2w`h}7 zvZ6~TYEKT!Pq@|kl0~@=&R#zq_q+22Yf%oO-+IS*&+~qE{mt4^Jt>ESuk5RjSMR_5 z;QQ{0jT_JwuCP#XAoROkNRi{lg2Vl0n2kA<(SsagsZ|f!&5O2zD#qtn5vS3(xJ9qd zojRq$Q1w#THx5)R$n<%4#f}h6@+4-cmVi^f9eB+X<1M=Z?Vd)}Bju(F>gVnC31N z6y&_v)6Ex~4eu-Wl#>@Y)zkrxp~C4tSS7?Z-~JZp_Bx`)gAaO}`5QeSEdYSv_lOe% z`!Wu^1cBh@DW*BjB=s9hjq$pHjuPjVOHi1EK_^rFI$qxvYxalEnZx8a&$iZv){XL_}xRa zcoZCQj(6xu!TJ4U9SiyRJ9BbR9gAS8t!%NQ@K;q@*d0>_)LvX2GL5vr%=3P!nrj4l5E)#==$L;=;1>ucnFGa}JuiDvz+A zPW^Fz2Yt3Z^7~>^E;_#&{Xz-?4oH%FK6SIU2UgXesQV!Ka7~%#`pygL8(H#3Y6CX6 z<|zI8ejdwh3>C?IIDMuIzv=#^Zrr@>E8cFexn!uKql>?fcxl^*+n;yJe_XsS%8xE_SJ6!_n|d#YvHC;8n@ zfg>gdsH2CK|uwb8nF(#uRcd4B9Cp7eIHX zb(PJyY>>y%#8)nk=2RpEzV5EW&NQSuj)gJ?pfBwRC*1TEMM3_LCZ2LH>BSdPA(<4x zhies5A9HSxHxG!&&{XuIrj~k`jb3f9ITF8Y!JwVi9jZqwPWkug4f1JA&XX)mJKx4m zRZ@E5cc?Gqf_A-e_?A4MS{6k$ipx4z!^PAB$=AmOwTvGE6HoD(Kcq?mD{CtWt8A8t z2ES*POuaFrHWQo>5K62=UOiA0g|e8aiY{w8>Y1Os-}H8!)||}XN;vo7bIvMP>fntj zhkVK@w(mfU0fR%CHcJh4Ee1L(dv_I+!zaA)b3icgX7z_AV-0M$5l(QCH*4-QZ!BOW zG1NO$IMw0$eZJTAbdDVQV|z!!}P7#MmCKmLiS?&!*ohMC}?Qxfeyr52j|V;k;}9q zN&}*CAcUkzt~*Y(zOQqz38_6Z-Mt;k{UD4O6@BIDCl-QLe#lF7SX7U2JD2;&X_b?# zm5o!+#t~$XKcGVSNhm}k+ijHPy>Ep(*7s~k&(Q=84$sT9)>JJG2idr287~r=(@c9P zTFhqB5xo1>q>hjKL5+XDoSpzhG|_q3UBy;orX9|w^}`O+{ZngyyTK0x_YQU!ax+NXvd&VRN!W%N2K5fL z#9Bq9gCr=)C4)X&oI04IZ*RG5R~ut|zyo?V)OC-_n95&9YtTLv0E;2lxWfF7;d=6# zXq2Ktq}8RK=O2A{=swAoS3mdOVzMy-)wPVy5dj-_qYW%Hp&v7GrMvPda^kjYHQi#5 zps+y$b@1BtKK?zn%Ahc!O?FIEpB5P)>Uzy(l}@im4*;n5=7j*a;G5806;o?A)=1?C zmuLJklwQ+ki)E2QH0Bi^x?1(K9UmI_GoQYi&-_90B-|Y(6{f8 z_C#tgm6p}9uO~-7ouedCkB}MGSO}(ZL@VGlM?-&|!a`1HAf&n&7M>WkmECw~%S5Xz zw6=W}(bh-~w4%*awxdUxhoOc<4~GMT3>_9I(G5gO&n}J`NZyh8pABM^TPR5MpcQ-t z`K!|ZeuWsNu^e17r`@orx)Q^G{fyYZE`Ef8QOLLt)6fuN{GSa3SkOgE#C1R%&41nW zKhFN=89D<4HH!W6lX2qztsz`WjA1UE8E+t2;{Tof&pB)qdCAsB^dAQb{;h!$Q*E?? zGFqCW=zl)#|GvimevSX^5&zq%{=bY3F)=CEQ`OZmD@Qq`qC)rl^d;aGIUboz3N9|o zPvz64$Q^7f=56!G=Qe>~y8%8^HeY|0<#!tP<6{vi7__-#AK~Jz7Y`1~C<R5U+7Zyv=RjDwvpT@rWZtviLZjWh?Z!f2PuRXQo{!e}5gW2wm z^?YcV>(kEDLU7YF$s;Nuf9f-Ukn&i{Rf!}vgD-Y>vgwxYo@%DN-rE`5twt~2P@;Gx zdR6<}LqWdCKO}tSHxqwnZ{5(7Ys&cfMm_#w7V2nM&cUou3zK0Q=aISj*@R|Z($O;N z2DNZV&g1A}rf|qkM!=iE+wR|YIW=8zBmegkoJGn`9lqD4+o!MZMrIFBqx%<{e19vN`qCZLXcp`0lhaa^&UhqiQvKY=IWTmDT4WDyb-rsq1@i(s@rn>%; z{;J?+csm07%oV7L(&el^d!9{om7p95Z4$2R3>-cLhe(#WEOPgDBf=6ps41k`C(o3K z+HJNpO^m4i5J_VN<>q&crTvIAd+w0_Jm7Yci7$X^(>dpTUwo3^%BoKD1EDvTEiVp~ zE4+U%E|GRF9QNJye4eAkg$uL*6BQL%gkq^G@K(iSNWfviJn^GB(0-FKPuu_A40Asu zUS!g)fSh!rD^0}rMcL`XX45~TmzwIR;Ne{vR8%C9Q}F&C11c2U_c1er^?TlaX$Y8N z)iz(mig0_0kBKXux3^9`IBO-~_T|1K<@?+~M-cp7ph(TM#2gtJ-QLZ(MBDOi&qAYQ z5(k%bj;v{*eW5OY9kYot__053UKq@Kwh698bKnht5BdFJ?$7jjCGk?A_Qq>|tkJ?< zuS^2ZIuW~TAwejaXvgN!gWZ157tVIg1J9ZO^VN}hCgQ?BjIGKGjnYSK!^Z{M-s z2F4GA=VcLsl)sBSR8Wj!d5Ch@G|&P=u)ytu38u>VTBCujyG58#lVtFmla!PUriGNd zoeo>Fa0#{50gByzd-lk$MLPOToxMcD0MKS+`<}b7}rpC`vkt3TI^SC(HBb$H|22cr;i2^0z4cx zB@j6uh8J~xSAEbQQ0f0*n1AEtD8n`x)zIzKliKS?1I~QBh!?tJMk5ePp3LP}9^3R9 ziv*GYkZpd`H2KQhaN8Q#v3Qt99Nu10bANrsQBY9H|IM_~DAjbBKXkP7#N~QAEs|3w zUt&@~rv~(dQ|(Ee*~r$T|Eo);IuJhl9i*yeKM8+4n%W?d^L=pMe|y3gyPIxj(Fh=vR@a?sH;?`VHo6)hp~RGDHw<;?H2>MfxH|4*_oqO=C{ic~1pP zMO|ZK4BT~A^LOn?1{+Z=;E-~TxIx<;g?<_y{!9~Ed6{f=CwE|palVkyZNG~vyGw-H z*CC}$m36PtvD}gllYym9Cp6r3#)b@5W;7(;gswTCJIIjt)Qq_tQq7(q2u0!jnIsE{ za-RDG`>31^7F)~on!+L@_!FtJ*k;R9`6N|n1JF!H^gNm8|Kf`CBm@O^JWo#lg^r4f zGsRQ&lx|EoH#<9&W7or*BP%m7y#rE3-Y92evL0(o!Hcc}3lzoTFAnoESJqaf_Xr;o z0Wv-8bm;Ue^Nf|uQPe%n3mtKXTOD)moz~&_lkk<^RCD-qSH&z<3KA{hotKUq4ExPa zU^UZsi^q4`pp}r3S`_fveC`mgmda5)7s@Zhg6}ZT(GosMDGT*!M9Z?!Q&1#4Wz!ua zX!CKL#ovF?)>Z_FT;aM`BjISS9D!fvWG^~4cKkdxTQUM9hw&TKTiR37QaA>_LQ<)OzyTBpZpy+$)aEXJ~{Z)Jst@fQ=Bc=PB0EMy^-+QevZC3vU7(I zKKUXr1q@=XU5%|Mh+Al1e%P2|^o#Ui^#5O+Bj>{n7$KC zurekTlx~*hzD1yxsBZZ5VZoh=xV*@4L?fgn1og>is;Oa6Ze~>68d+*{`P7Na%<6X3 zbgw1_czYojI=)C6q4`s6o?)(ll=KmiP4)?3XCRu^KNkC{NfzU{PqM3%Ank0w_9hLO zhre8Z*znXRbZ(&xMq263!-RFr%MFb!6yrU7fM1^w0UzW&j{7agxt`NZ3WlOP>FMc# zhx9c$1@XVXgM}z!n9O;0f=m#(is(hRJns9|1uUBKR{M}IbXMTH5SJ02ke3mf^;qDY zg|q4%%N&0jOr43x-#%jq>wG0G3F9SxgL9R-r6k}B3VceP62;~u>;knKLurlJ+Y0go z*fZc8hw;RD1pUqj3m)yX&UsdTxpw#@MGT8mgCXkiob1^yD^qPzi%QCo`G>7->KdSp z%Hp=y+c!`3UW5xjMW>WO8%#PSAaO92$qA{fOmkK);)G^{ii|o+hSP_#mY4t81ex)m zm;Zckhgs9xs<4KrXOWFnM4TR7EtEH)Q-fQL-k!38yYCO&K7e`;+}`w!p~2>~eyy^k ztN@PP(dTlvD~l(T5a+PU`Y3q-+4fJI@((R4ls$DA)z1}9nik0LJ~u0>AFkpu*`?_> zkl5`e!8l_u7^#_lg3Y=DOhR*GOgT0mkTDtglHW^T&lCX!FguJ{E+KKd`^t9<;xED2Iqgcpk4Tq6Xuh3n(NXi%j&9Fi#*5$Uk zeK`9;)<+PX`b?1UD&fgwFB>-O52bfmgTn#;mId=@htM7 zF3~6dUj+SCdQbVo)WBP78FsRUKO1bKd!{6|(_5{--JmEcs8>jvA{3!i6q6m#!gs^C+ zKgEc(T}XF6!CGx*E%;NNd+qLC7okus$B6iq{oSJ6A+jgW^M^&K)n`vJY0@c`lFTFnx-@PGtPZ)l3>pYyjt&v4X8mL5W}e} zDlC*62B6-GPd3mAw=z)8`5GHXxWmC%h-;++=_j|KN0DP#4phjnQ*BWZhU!icmXpwS zC<6`$qS^2Fat(7R_40>JF45LF6k(egd~kZ7Vh-MEb6@B~BepGgn7*>7=2eG^mswu; zuNlSR??ZVZ^*+qN`t&#cYrq{|RYerZ4Zj9K6Qc4z%4S1NU4FRWGDgZ4ahJN~1>StR z$r)f_J}gyix(+NgpsdFPJk!%h>a}dP_sj4TL`^S-oSKALYPK3hz@RT9kQJB-PVed5 zTn$U86&|qo%MqY8RTx;eI$TIp17?zn7OsvOwdVs+g-n>o_U@4lEDjHysE}JM^Ytek z|HeMU7a)#GPp&{SH4x4>BA=f?G9OG;c-6d&MsOkoGu8j~_jhN8fvm5{3b{?{-y32MjuA ztO#vE6m3{jTyEJzcKJ+?3%rCzVR)Ud((j~)%R(}~OB~BShrgjmWyMk(7gv+$nVXzA z>35(f8aa&)n>?g7GIRBDoI;OQWew%TGYGo9Mjk6>S?Nb^Z)kyu2!1{3jS8(97R2!> z3_?f1AtPgWeB1*EL~cHo8jtf8q+-$`Fg!-6p@VElm_Kb9DXzGhfCpZ_Ax5u>sY%S3 zh_m+~PNb3(J%$Iu;jXtq5QsTlDTK2W2`h2TLG?l@q<%!;0S?m#04&C=QQ3ag(dN_W zT*b0~yIDKhrQ#cv(mlvST`kvKxWh|-d3uQ?j2Ehsz4&{=mbz!sir9k;dqVrDyyZUYM&t5T>j00~d|9i%Y*9Fres@#6Ke{>|E%OGiB_6N@dIa zZ-J^Foy`;4J}MXaH*B~bL6a5ReB&Of*>81QgftGU??PV8(X&1eMLEo7>B8dV?=Q;U z=V2FWf5jyUxRRK#_0NCgH;q1oJ&5*)C%>?q2LK5ob;MXqFwmp(sY9Dk;bpL@Dp$LF z5j;L5BcdWFM0{AoqnIM8^Z=bcN9Z6Y*EgnMTJx>sTz2NHCBF*1kPe>#;7&-MQ(0_X z6U|k5h7T(JAgh;}+UW>JGCa;#)Vl4Ec>a}hzk$GGG-FDVlC zLpKu3=gipbfjko=iH|a^!MEm#Zs0&u>v)gx^I%&O2C{Tf_N`Pd?K+j);X`V3a*H40 z#M%Kg7La;%g{VV*KvBDW=qdew22zX!d+;`~6K#Db)O&XXnO1_GqiRwenr|^34yFW!K-Y=!^nL3)nbthhtNI2K3S`gUJ}lcdHdGaxmY%H!NP~Fm;;r1iLL@ z)@do2N5o@h(;#WLcJIwT)%WfogNPPJ=7!>XMA zs=$1FjkV?h(w?bHw_4}Z=cLsK1{M*A&iKHqLfd?J->Z?KR+(m*xc$v?LxKFDH^$U! z^#KpHOm0_%y{BuO%Mr#T4JL+_Bae{Zzm6W|^W(a0QF5<59CrXk9E0sZ8i7sj_KJYP z$pIu%g4!J}y~{iUgJjuK1&F}Y)T_5Sml{h}d@bzC_&t+o`FCEi%sURAYGl}c@nqPk z6-Hrw+tBh{k06!JU9vc$L22%dFBw~?~u9TL|`SDMdEeqqG|9KBTMHuL{=@C zyj|J&>E0rM`T?uKz&y)}J^BmF;C7Wy=jn5;$~IMV{5YYfqR1P*RGU{)A-ge7$?p_ zsQocvG{_h|t)YK77nOUQ@16BHpLa@B>X|s9y3jS^&**IMAKkouA<<6GFBsH7 z;H7vhA4#nDJs;-9x&EAEzlgRR`3QcTHhLGL2f*2-tW$&W} z&;m3pdt85l+n_*&Tma)nIOv$`I$HV*3cK|Pa?aAiQjq6WHwrmW zGdQTxRbIe**gT~*-7n|)mYkli7YZo&%`SH0e)z$yTx366>V9s@@QKAF({r{VBh*K? z_Y?~6&MR17=Yqsel~dVneP&b{>?_F;JVl=jM~||AK;0s-MIJ?uik3dohYDyChJ%;`a???ND_CUx%CFW5axZU^yIGy$Dwe#aITGXw=3rs-Ck;j5#t4^+LqPI416!C*_Ti+Mda_H{?DRaMJ1ij%9xC751c)*l8P>P%vjqP zcz=O;rLv{pz|Z&C;)Vft@uuyxw#&E6QGkAMKDF`%`C1x-|HCuz?z{J{or^r-86J7*bP;I;q_;l&}-qPo1|_j^Wl802Lp2_fpAcs zacJ!nyG+s+?m0eiFVvdY2QEMvNsm50PGDAV$StEJq$V9R?nRE=DPP>M(^F*+wga6Q zq8ZCKi46b#v8=IU)bYOa2B;o}3nOB9iAK)vAC=A;BEkD(uK#D7+#trBwiW%QIy|%_ zP<}fu7-9FqlE^^91SDsIb_->`d!e!^fvngrQuQS+ z)#1!fZ*g+ItTmbp2gU!DK=l+eTDX53h2D~VTIYpJ$7aAn3wfeRC&dm(x0A;%W@Q1MEUjEPsjLgnN4jjOJq@ji-BJ=EIP z3I{=+e(XENrb%n~xjAFnQ*Vl-4LH>#`^~)!M3m8uPE4Wn(bVV zI;v(Vg;I7eKKK)7{!+(3`$;FBp`ZLboGNU&-)j`iR>(lC6>n=jeNoZYqILptf8aY)>habr%M{aDrUa})E_>*{;VG~1s2SVR1|Pq9XeKNc9hW4b(6yNl8IZ{Pmu z=v>U9OHKzHh-A3e#7D-7wJbYS+mK*^56=MRrlDZ24#4T) zO{DU-*w@uv9pdU13ucq-FrUcNm#t1JkG0u8WdDVPfx}gCwP*FzU~W(-DO(M?B{G0O z%l{D%jZ8#lIG|N#=r;Ih&X7E~WO7&oeD9s^m(4-#LjIsRjctsUmV$gFD?Jn?4m}Eg zMZNFD*T4`xb!wLO5cU#qzaNk`E0K!a|K^2Bg*QXMlPEkQ>RY- z1a(W;CDo=pO14Z7{|K`a$(&RWHdm-jg*|C;?C5>y|52uPSxOkU*O zXq%J3RjdZs{P-twoAh7qHf$%8UqlTizmczE0Va|AI#hagaP_T@pt_GGx=Co>S5yYV zPbF!Vj{^cLISFx}py|dRmyT~JeAv~J{rqg|hPA*oL+xa#3qra>DEu(^`1IqIiEd=N zDDemPo=tbG`r<*k{c_>fXiN5E=JXV!SyYBNvIcsXqHh=AI=}V zd2*0qPH^mMdiQ%bp>B%lMK4r|8pRhov*911-mM*P5+bVC2A03*X#Ptp;6VzrgwmeA zh}B{`cu)l^3mR5K>W%_r!}_lqachj;;#c;6@yGoSNx^^ddj0S~MUNXtLTyGWh|u7_ zj{b-40PqjRC#RzXuU5udgj~HRxSzZ6%#RyO^ z6Y;M%(BZLzR6H$!{hMQ0*rzQFY2QaB(l0kNzm6}FzYzYX3Id{OAcihDM3*_N7mf^T zzb2{diF05wk$i7`zbgdzxp_`51(Tv|bn){zGz!w>q z=!)p@u$(j4Yw&dUEo*2C)2vi49+j?KZ)|Jb=N{{zb64JU$UKwh!0`HD_p^b#`V0pS zCLt&iYsf7wTPUR1S$6?0nmg{pm#AA(^FVlZXr6_=U*5)~)okx%ydc^l(5;i;Nv+cr zxAmYPT|?Nu=ScT_v`6U2C=@-mhs%3Sb|7v>6+hafq6{z-(?pQLbTnm8~s%JM7%@ zhf1VzLr9#+ObJ~I-ezhu^QPn3v}U0b+-azFz12eS#|a!)DR$G-ehUH}D!W~2F=Ll_ z(yi>&4aFbgYz6%eXJX`%z7b2pP%ELy)*7Xw; zlAg_aTSP8JqS&<-D7zlpeus~1P`1(8k*b>r*4>3!rX4CxKJ%%vWTLd<1y%xTBhrPT$`!^X;lX*C78V>$Ji+VBZg zJpvnLkLhTa!HzJVJ3<>$F*fuQ%ll{B6UVcs^=!9h2-#!#X$8O&hIm1O)(-d;&xfn^ zAXWSf+OFg{dzN4MaYbIn<)61=yBfXwzskv_*O;1xuHr?RE8hK!o?Wn(7?F#*(f+P2 z?^{k9Dow3y)D2ERYS0L5oXT#Mv21wEAIb!Cn(qUT_aq5Y z-FU%=K*fpuqvAsMUC#64Yfl5*ZHW8I>MqJd+7FWu$|ZY|lfEYCtWOkFITX!kN?38=S$%lVVzOUaUYW1Bd{9lsQ7^s6B=84%!ZjCaYv zKhcWx2tLL|#uhN8v8jJuJa;|n^%~3j$ju$YawiH$u@UfK&m939CHTI(JxcTMdag04?N(W!DManZo2&3@>fji%bwEaR1QI} z+sXFfsmHQfUU^?@a#^xA?i8}Jzk*IB3MXS_F1$516hkXP^6#5?z$+jX8#Vz;uq zyr@s8rOk0Nl&RPg5uJEqD!8<*EsC?*uJA;=dZ{w4boXKHfqh?JNR=**WXoG9`={BC zQj5z|T3X8NwqLsxVaU|R8^1r9pQIIz%eoj5BsT{hs=1-sqR9ZXFa!YJE?+nAQ*Y}A z2?atJeUabVpqTek6vW};^JH6z%B?1Fr-3b*46(vsA^BW%K%mtrPPkH$DeJwD=%&PrNFybr($dPQf8kuBV($_BdG;J zeN)^i+o*%{7V1XKUt=lA zqH_^3p9aN&Va&$K0%Cz4FqFNM7&*f)P3Z0_Sm)c zW>w{m8$&^`WUw^fp2razQWJ%Et$r-qIOL4*6kE#a?uM zF~H$kV^%5g;la#i2*u*`BB`Kno=#R>3GBRM1{BKe83Z^}p^qyKISF-7U`pf)IIWN% z$Hx?ghkFkupMB5BGa{Cs0tbyMZLyeohl4>t z0U@Y6KzGs0^*a2XBj8T#;o61Y~7&W5++evos688$AXzZs_YUqp{_Q}) zgqy@-VlFgNX3#>ZQsNbzs)67khq2&vMQc?1q)1kXX-4ZPnf1$-pBL+nMopuOpag$vleuEWx8+#wb4p;9#dI<}A4 zOA!mtjk8yIB7#kNIj{48VZogs`}kyfSrd7}8;YPG)XghEhYB}!b=n6JSzwQz1MvMvTk zXcLlJKWt1BXH!~h$k9l)rz}}|;B5^q^f9A)ntAH;enwiJBShi zK~1Ow4nxILIt+YF3q5)DGM=}9nOah->xw6;4nsp@)F4yV7iQO`dka&$-yaDqpHT@U zO5#Z|>xzSL7c*>uH95kKzhz$21qsUY`p?!IrN`~_mDI@Cr2+-|>7v{dd>Hgz7yOrO zsMW|U>y^Syknf0L3v$3OkCUeL63gigH-)| zt8^u9q_gZNDr@uRSZUX^@tK&hB`Kq_`R9Io@lb6Gs?f3_FAn14|^(T!FU3)n@x&u__~M7JDj3YiPXb zu<$q%n@Btb9@lc6wH!74!MdI=FKbxSX!w)+lh6q40=t<~xI)e>tZg7K*wjBRCGpXl zk)(pPcfIBxB`p`1mbz10)_0c4nvPli%vdUX)ix2Ym&$XY#Qp7PcNv%_TU3lbLWN5YtT zdwmV#L=3ZM($irlT9}3Cat03VW@Gr7(=SjBF^Yo_VF(5!)tj2A0-9b)uSn2Hr+gd- zhQ8s$HRD`DgM9ja#V=pqrf?-VfIcV%}k*}bV3C- zkTF7`Q9U<%k-;=zZ-%~M2PETV1Q-B9ZS|XNzHQ&;l6lw$MexsaIcZ=gQ`bzH;z@dA zip@}QW2(APg}{dx{++LAdW0SndVAtc*O4#YpumHL(98c?$6Z7H-~B_ElRC!mSwP_p zXWOO|`2MSXn5=rU8jHhR*gyh!c!Z;)-$r`Yqq!oJP*9XZ@>1fT37!mXI0<1FQJcn1 z@8LFUG$ce!x1xALGXNOJT+Cq_;!ZSEaZQAJb%d~kAsGqT2T~ddc2=@#KWOYsPs-Ln zNZZ?;bbSRPqha>LY2c2$Bm@M(Wlz}P@z%dHB_OPcPRI}))&)v&ydbO~>GW~6X)FvQ z**0SGLq$ZVe@;~_le>hq;lIwE@d&KhfasKx)pa`08Zl8K9c`&yTW(?RSR*A+5Ig zzygN^uFMndN(@eY8jAzTNqu9y0>uZ*utstOTw#9L0z_1!im_;aC_m?eD(jz}u&_F; zdJ9$N1;(Xn6=yV?JNMseu|2~J6AF7`<@541|6}$)MX%7?X65O#5!@3=y(N*p5N9mZpZZ!Nc z+s+8Ik^OMyFm_Pv?{^P=_&`WqIi3#Q7=Mi06Sw0&!C45+_*Q^;U+2#$hI$!a(|cjx zMh#l~@x4hsIVBlsA64FzCL9w5$BL!rd7D0a8yNwmPmU}x1|mlZ?93|hH@7^q5|TlR zz&zWJ_E@9a+JTlv@yZZ*A=9!@JbYbF>J{$A^jEDJgJn*f)nM8rWH%30O9hz?*ap*l z{v6XpIZnAJ79<|TY&#no+`P9<*DvgAJSAf(r?R?cQQk_UlogU^t|$RXV;|iRXd;UQ zL>F72eq+SD)@1~RiH27yQ2btnGE=v zFYB1LAOY_uDFQHOf>Xmo`zL=BlOMhH1WR6sXGqy(%)mPIW?ny~EQRkNIFh^LYSatG z&B~EI#e=E3(_xHJ?&3yOa#6-Qy4CXJ{kqC-(73vD@r2c`)QX(yi2xmf8HpU@a4EJ% zV(NCjlzi}&EK%U?b?_tOXeP16=!E7fCoA~Ev8lyiyC;WcUUgV^TtGouNu>V4KxanC zPo1{nQ6l5KcVPu$yRQ2FHY5)EMC=Ep&;C)I?5oH2Y}Jc6F!Ez>UmNRKd*d_p?xO#y zjou3TwQlq$^8ylP6CPq#uJ06mjc=J9Y+dT)TXl#MhU_S48PRo!%O@`EC|uB003qs- zcP@xtEO-odb_o2#cFq{xY3xxt6y3Mm$~`<*=lfTiuX~`ZP7@_!A=8GM4YO$gY}RKv z(nt(~+kG9JL)dNBTzRbH6hob1$EHpb>%o|RM4ZicOMk$= z_2nmMJOq?~yT7ast@xH@^R5>w2@2GzJKnUFCO~pt%4F5UjPVp>u}i~8-={~;4cy+1 z0yf&POA1iez51B6t>)G8DnBe31079p2?y;_F>n?ZEKWQw^};2?zK42x-w}7GrrcFH z?#%>J_LcFfX2^Xt{VQtGd+X5oK|C^mJ`$13V=2AXbk6uO6~mUGo|M&dlm7C`0rr?dtS&VbUZBj-O2^UjJ#|IRDl{d0_NPB z@NMFwKJ8gDCk}oojh=-*E#gG5FI9{oq1A4U*E(|7wUf=OM0)l6p@E5s;y+-~tLLnS z1Di#}KW>N0%ayys@GZsZY1RWW&2|=*CM8JM0ObeJ9t#Xj-Dffz+68q)=ic4rQf-Xb zjnc9#N%LD2;dOK?6r!S}vSvDI`}zVthYwYtSn%eDp6tWJj!g=Eo5sR1Z_!?+wlnx; z0k3u)ewpB;hU8``3Zkw_3T#rMpxy%5N1rw(GTEBIk!wtD8C=r84cQHVE(~q%5HGB+ zYVhOh9};;VM-&tVP)Mrh}(tw=&aHc8RCJD(jr{q0| zCFUjbMex#v8{M*RozRr;rC8-rmn^Z!^^Em3#WgrmRxN@3xEl$n7uCNUsQ-Ojp|$`n zSA*F;#ylvTfj35rV-!xloUC|({TR+<)9SJE3EAWp1)c!q13SwHrtqepKe=RgQ=_LU zZr~r#9t9P@rx2avSg}K-h@p@@2d5&O&4QPSmGvdEqHm0)nCuMt}=l36wC-b+)b zU8C}eqpr>@QCBH8aBFX9VF#iP55+|QAw2&*%Z^OLk#33zL0FbD$28wG|Dq1J45#xM z4}=Ntrf(rL^<%f=?_qZH4=#s6uc?wafv7yH=scLZ5n67f;W(76Z-J;=Yiz#?h9^fy zM~}mfz6Lz}I3FF2iHUjTyxAb;wKpGVb#z!0vfY1OKr!&;Q& z#N$X$x6~1&nTm{0l`3Q;#W_+yqrmGv%l-E?Q6%BZtgDwUX-o^dH}8rqG&Q_b{E({u za0$;xX+#Fqy*rVti*6dGQ7+CwDM6Yu{cmKum+X-k*?pFousB_5ul@E}%&J5NpBM9p zcfaown&dhv9}Xwk7}(BQ)>?+jA1;ks^E^jZ8av}-)5MdD;5cZEToj$>^2LA0wP3Ds zn-g%z`orSAK}$8&i22LwQyZTd{HFnhql}ttA7Q={y78Vrf;^l^7o-PY+V_zj*zft4 zJOmJRXeJPs?6Qqyguk_D>qVJreNeX$K>AY5mO+fOkn$@8T{bt<=f{inbH!7UJHwKS zY?`IbT$}3XBfxd8~a{^TjeWyF9~Y_~(Y^={@` zl8%gBi#?Cl)O1&cS&OSSTLkkH4F&<)!n6#YQ{C8CzhvWYK2HN1iWW};3?H_t_!VAx zya(ZbJ^iID?=n)|2Gm!3PCq37@fL3QRC}!tMl(bf`rNe6L=?cpQLTQME-s(Jiqq14 z%PiN}yJA*F_b@x!c#3M$ajhj3j^>{KbX^6OusG7Zd+oCK9!4Wim?q^7mQBg0TriW7 zdrM_GA%tCo`@ubW6@_3P^O8-Xqul}0nERc_pRP_j|pcVmSG7y$5%13hB+qingi*D-=tPQ z+>GWk~O0H*^-w{B(v-2FB4r&V@8joI?EfA13@{;uZs z`{*BEFZ|aXa74rM-8j0Dd@EXq)_Dh2;piEmlSKhsoE46uUuN`-$ zhCYv-x1+ZA3%mN@SCK2o>6pB;>`B$&t3GiuGjnEdyhvZEb!RK^s^gOS@3K<7{ zQUOg(nOzf&j=ATVGJ-4y$)>O96m90rpCV(wD?z%|6lvgS*K2flHkdkZ4f??fYja;s z;*;gQMN8F#op``Mf>(#Xxh8Onc*-)+QP3MTwn~NRMs!=0ETr*DXH?2K*t4(RuI?_M zRz@nbv4lO}co?pJAG=}2Fl&5z>;M$|Jky-#@T>J6iZXH%wUx5oZAtz)DgPA=g@2gZ z6B?f0&#!@0Zx&lV>TU;6`y~p}*>nOj`y_b;k}PLxyt840`Z`*NMjDQ}*e-XpuTAUC zuhA#AhtrWR%l`4A8V+K5BSKPgs^R>iAE@g2H+n-BvE97Q=ge-Dq>A4L!qhM&#>~X6i8s&2DU3+#D#JYGtqFoL3&er zrCoe4a_@dIO8Nn<1j|ftwUG)45A9&_969lCaT$K^6QiQ;q^fb4v3w0i((*Fr#M`l}g+mnUqrM|*v|xMn3S@})d@AP*9(t9AxB zb?mL*d!~_+S1YZ(K`E*w%onfs@;Om|_2+R7NjmXI-tb$32Szprx>W_O8FFt_BWQ0% zsMRA=Mh6NJ)YyT>`DI-9g)zH5#&nZa36vucp^)O9_d{%l)xOVPW(OE?kOMhlRHaG- zA0F;PkLm6XJsS3baM)ww3v8=uk{rxf7Ekjaxh0-q^~SE8l*eyuXG`D6;#>6z&{7U= zs5?pZ%}hs6lh#-{_~3!aQ%JU|!9W{_@O->E7z6%nIM66zC2wlHl0Nr`m|m|hVKuwg z)JI!DU<*X)w`e8rwxA;(T^l(r;8&6MF<|obuoVf)Y-tg`d7z3=Q-%byjhja^!56}3 zpr5E-%aNIXL1w5gfh_bka0bpKR>{?SPLX%w;YQGAUHo2ka=TvJjq*yK(nMKad$I~P z`|9P$2k8{P=924(%IJ(l@2^=PTcW|O4F>B4 z13g9tvj2dc7kPU8)m@OfY3cMA?-1pg8-Boo87ik8&zFmn_qU88J-a#=M|}RdA?zEU z+Y{yfdl171Rqo%f1Hua|s$F=$^vXeHN23mNzMK3H&HqsTum;Z2n*+?ARc_F{P$XCR zU+91bXfXU!{crrHs^XBxY=gbnzX*^q>ROo)-cv>V3&xV(bB}uR6`uclJp71!MeB0Y zcpH29UjgSqC9!jVjQYizEvKcU8@d1yIq_|WUw0#(0O62PxnEz-je`o zHW>mw_D6IG2mu=|^2t)>;74F022*2?Ynq)Kt#zL5g`8{zz+0KGSOIS(Pfx((#|zur zW991{?c!N#Dh~q^6pST{%&&MeNe#8eYw<4N^$5+(A|?&EA1S=T#qM)A_0ZqrWlafwkV`!9o%x6)9HM8(q9H4Wz2$c zK^|KmgP9SsT4%-D&0uQu93I)Fr@tpbmIGb*l1-z zaq@245p2@-=`N(t`9a<^F+cGqamr=$9i11GT*cnbp7J}XRG#r|Zm@9E01hNQ*ZxRO zd;2lY2R1VkI@}mc+2%e!CC-_45Hb(+?;OSVb>1Mt#HKWauK4{px?^F{I}IM#;i2g; z8lEzne3adE?(a=Hh}i6Lm@I2jCK1x$kj2~vw^@k6IC@s{??`{r_+>x9XVY7(*|Qk* z6hW+=jG+fgU~6}OcY0v%dAnX3;$frXJt0G+kG4<`HXy{>nsEBon}%ai73}l7l{k6n zHCYBJu5}PRe>13)Gw-~W^=0U7_*~#}IfYX2qww#6Im+Vi@J!Cj7)ktl@hywGg-QYV zvQ#N#hc3VXGUiMH;i!fAdpiwJv!X=@`Lkq?TBjQcGs7TgQ9iDcZ`5W8hG_DT%-H)f zNysic>o`!aHt0yVJ1)we)+{ryFrog|_F91~-}m(ypYSDl8>V$s=&!`3D#F};v`O)s z(g5gZmaBI35=gpF*ZJ|~m+Zbh+or{S?o$eGWCc{f~; z0Y&~)IV(tf0W4_%sMOu{R9k&d+K8@#ey2#pj z*|r!mPZm5gMOkp_<|qCo(~k=}>lC@bUN$|GuW$74jRb3@ z*p1dXl}Z_=-5AccH`t9BHJ&1^deyI#8qQ9S7S5=^KF3WsVVb;lFr?KEI$Pf)1f`bD zoOY?Hzw##-d|Xbb80a%;usgMRla>Q-f^|y>IdUq&S5LSZx7&n@{lrZ~IV5~`Ku|>} zQrxj~XbP5P32&d%6l&N0G7}dS4H~ z*T_5Ldc{{{n(Lh0WO|j5s)Sp=*2l87vgujfyp2{Z309Uf07fW~Kob z^zt4XOQC0bM(>ryNv@|W-gC}T0RQ&~pz*_V3hMO*b*5{AHuWFNqA`sd)i~0b^+%<3 zOf;gY;RgVBQm1ls)}S#OsJIgBZBCH76&4&8`DYdFc&}4vt&_ow=rip<@nL<@DQ8~q zGM$#}ML`BCe-O_7s5D!htzu5{rQqXVB51#!Z@qo7j6Ac1`7BrHE_S)yg?I+wvV7s2 zs}hk~L1*m1h!qe{A1Ma62l%#sUE`uVoDs-|O$z^PSwUSMj4WYAmLj>&`tEKFr-&M_ zkP-zEmId3phplk;lR77pU96&{ifw=EoZSdzh}~%sJu}Igd+AC^M7WV6rnQH?A$>=6 zsI9APF-E=ZZ)_gL*6m=3$E6a5&$J_K7%b(E&g<7s+P6<>8@kxUGhvZE)&RSj(yMP* z%Dmgj>11-hDB~D-*~`{=4(?vM`7spp5e!9@+u!lwCdQ#`9C(UC0ylJuu+< z@eF<#yK4po1R@HWzr@|*u^97R{0Rm&1!k;nJ&k{(b&Q`|LM;nr-_a{6dGM4${%-@2nS|*)Jbl9_-q{+RBA4juaRrW1lx3iQmIC2XW`crh$W!8woQaD z60{B7JWwl+vAeDpEgv?!LCcB=&+*75bFc$@uUN*ecjb2CW2F@RkzZd_}IZ zV5oj=Rb+}OL-drr@UG@E?25!o~eQkmgZ2E9d_$! zool>((||(uAh(C^&rqtGpR8UXrI9fWU?)6HE{(dl>G%+TEEli=1fA`nX1UFMMvOA~ zZggehg^tmZ@UG!(QYS)!PkBsMbUw)YCwq@n?f&7C_0s&L3EljSSGEn!c#h?Rl%NVr z<&~)RGuydNIue7PYF`}C!vbmq*{XJm3g&zhZ{xP#oT<-i7z>&U>D&v;5GNjYyJ=Dq zJh6#p+xxX2Og?gqzOWc|U+p!66F84AW}HOrZkjH#V?Ax)ZaoRrkQu z{bN1*D>sT?^Zk%4k39{E=^=isvDkmR-%R(zU9m$hK^O?DKucaoPaN@ZH*;-q^BDoG z1K5xLQo7uky9mYZ_1rAC?FWnU4YG6^vpeowrJ0zD3vR=o!1E7HndL1}I*Ae_2Ct3} z))%Pvoyty!Zip^D?P^ok=mBCnWBU;ECptPwp12*Xw`-w@x7_0~QdU$^{BwZoA6IFc zrC?x8z_KkD>1~hWz1&+IPR_av+%uhRp`NED0oONXM+ZP4gc9TRJlY&yy>*&ZzJ5KJ zf-j|D!lv%_J0~;;lc_GhOv4J|>!2?11|>agxaCA`aulr)ACkElOge6QmK-z)R1t2g z?vK9z(LUKBif7O&Q6SsDAN;q~IlZlpsfSfjj72r#tWk|cA)!3W@|Rmep>KFvi^KqX zCDpaw5U;1c@%{0clr^RYEK!5+!fLgO7fGB9F}-WCN*2b8hjpZ`ZmEr{F_Y!|thf6J}a2 zn6jzE-w}tlg4|Xf0*wFc*}`$&Jl@X8S!Q>eF^Qnw(7UuDW05X*<|Lj!phQq$f^$03SfjhNhzT}Rc3dCPyihj2 z2i86Ahs|PU2Qf##m@iRky)N~1<`Ra7zpB87HdbGlSlG{9YS zB|f}S%rfvSK@TG&1q#b~X%_Ej@Tg1jdsTmC%aWKVddrPFi2#Y)7-+Vk#VqMZOHg)c zXv5S;>#Us|oRoL8b#fh7_KP-v zb0bi#fe{ybISIJv-g3U%KQs4}XKKp}DDYHAI~w30@EQuYW)0@YPdt7K(J<6>LNQsS z#$>_KLP`6f>A$brKY%oQ5?shFxve0P%1m+`k}s~pLRKb4Xk2;+FJ8FmW)#`!nzPRebsn=&CEa96Yl6CixHQsyX)4`^*ik)Yt0&3ak~$Z{O-m6aD|L|7SSR b0V2D|I=(OY6k~Dis%X5>Q7L- # Blocking Strategies: DEFAULT vs WIDER +{% hint style="info" icon="right-long" %} **Enterprise only.** Controlled by the `blockingModel` config key (`DEFAULT` or `WIDER`, case-insensitive). If omitted, Zingg uses `DEFAULT`. Community/OSS has no such key - it always behaves like `DEFAULT`. +{% endhint %} ## What each strategy does -Zingg builds a blocking tree greedily. At every node it asks: *of the eligible field/hash-function combinations , which one splits the group into smaller pieces?* That combination becomes the split at this node, and the process recurses into each resulting group. +Zingg builds a blocking tree greedily. At every node it asks: *of the eligible field/hash-function combinations, which one splits the group into smaller pieces?* That combination becomes the split at this node, and the process recurses into each resulting group. The **strategy** controls only the *order* candidate fields are offered at each node - never the comparison logic itself: * **`DEFAULT`** - every node is offered your `fieldDefinition` list in the exact order you configured it. The field listed first is always tried first, everywhere in the tree. * **`WIDER`** - each node looks at which field its *parent* node just used, and pushes that field to the back of the candidate list for this node, so the very next field in line gets first consideration instead. This repeats going down the tree, so the field that "won" one level isn't automatically favored again immediately below it. +
DEFAULT vs WIDER field ordering down the blocking tree

DEFAULT vs WIDER field ordering down the blocking tree

+ ## When to use which
From 3fb09d7b39d4ca3d51f7e53605c8226a546ec474 Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Mon, 17 Aug 2026 14:54:51 +0530 Subject: [PATCH 057/144] Clarify NUMERIC match docs This change updates the NUMERIC match-type documentation to explain how numeric extraction works, including set-based deduplication, deterministic zero scores for blank/null values, and clearer scoring examples. It also fixes examples and related-match references to better reflect real usage for address fields and multi-field matching. --- .../match-types/numeric-match.md | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md index 0f01bdac3..6791e0364 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md @@ -9,13 +9,17 @@ description: >- ### What `NUMERIC` does -`NUMERIC` extracts all numeric characters from a string field value and compares the numbers found in both records. It scores based on how many extracted numbers are the same across the two values. +`NUMERIC` extracts all numbers from a string field value and scores the two values using Similarity functions - which gives a continuous score in \[0, 1]. + +Numbers are extracted into a set, so a number repeated within one value is only counted once - "10 Main St, Apt 10" contributes the number 10 a single time, not twice. This makes it useful for address fields where a number is embedded in a string - "42 Main St" and "42 Main Street" share the number 42, which `NUMERIC` extracts and matches. The alphabetic parts are not compared by `NUMERIC` - use `ONLY_ALPHABETS_FUZZY` or `FUZZY` for those. +**Exception:** every other match type in this reference treats a null/blank value as an automatic match. `NUMERIC` does not - a null or blank value extracts no numbers, so it has nothing to share with the other side and scores a deterministic non-match (0.0). + ### What `NUMERIC` matches and what it does not -
Value AValue BMatch?
42 Main St42 Main StreetYes - 42 extracted from both
42B Main St42 Main StYes - 42 extracted from both (B ignored)
42 Main St43 Main StNo - different numbers (42 vs 43)
Suite 12, Floor 3Suite 12, Floor 4Partial - 12 matches, 3 vs 4 differs. Score reflects partial overlap.
Flat 4, Tower AFlat 4, Tower AYes - 4 matches
[no numbers]42 Main StConfirm with team —
what score when one side has no numbers?
+
Value AValue BScoreNotes
42 Main St42 Main Street1.0 (Yes)42 extracted from both
42B Main St42 Main St1.0 (Yes)42 extracted from both - "B" isn't a digit, so it's dropped
42 Main St43 Main St0.0 (No){42} vs {43} share nothing
Suite 12, Floor 3Suite 12, Floor 40.33{12,3} vs {12,4} share one of three distinct numbers
10 Main St, Apt 1010 Main St1.0 (Yes)"10" appears twice on the left but dedupes to the set {10}, same as the right side
[no numbers]42 Main St0.0 (No)One side extracts no numbers, so there's nothing to intersect
[null]42 Main St0.0 (No)The null-matches-anything default every other match type uses does not apply here
### When to use `NUMERIC` @@ -25,8 +29,6 @@ This makes it useful for address fields where a number is embedded in a string - When your address data has the street number embedded in the same field as the street name - "42 Main Street"; use `NUMERIC` to match the number component. -For best results on address fields, combine `NUMERIC` with `ONLY_ALPHABETS_FUZZY` on the same field (or split the field into number and name before matching) -
@@ -45,7 +47,7 @@ Any field where a number is the primary discriminating element and it is embedde `NUMERIC` ignores alphabetic characters. "42 Main Street" and "42 Oak Avenue" both contain "42" - `NUMERIC` would score them as matching. The street name difference is invisible to NUMERIC. -Combine with `ONLY_ALPHABETS_FUZZY` or `FUZZY` so both the number and the text are compared. +Use `FUZZY` so both the number and the text are compared.
@@ -64,15 +66,16 @@ For "16gb", "500ml", and similar specifications where the unit matters as much a ```python from zingg.client import * - amount = FieldDefinition("amount", "double", MatchType.NUMERIC) +address = FieldDefinition("address", "string", MatchType.NUMERIC) ``` ### **Enterprise** ```python from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition +from zingg.client import * - amount = EFieldDefinition("amount", "double", MatchType.NUMERIC) +address = EFieldDefinition("address", "string", MatchType.NUMERIC) ``` {% endtab %} @@ -84,10 +87,10 @@ The JSON `fieldDefinition` block is identical for Community and Enterprise. Only ```json { "fieldDefinition" : [ { - "fieldName" : "amount", + "fieldName" : "address", "matchType" : "numeric", - "fields" : "amount", - "dataType" : "double" + "fields" : "address", + "dataType" : "string" } ] } ``` @@ -98,9 +101,8 @@ The JSON `fieldDefinition` block is identical for Community and Enterprise. Only {% hint style="success" icon="right-long" %} **Related matches:** -* `ONLY_ALPHABETS_FUZZY` - combine with NUMERIC for full address fields * `NUMERIC_WITH_UNITS` - for product codes that include units (16gb, 500ml) * `FUZZY` - if you want a single match type covering the whole string -**Read more**: [Match Types](./) +**Read more**: [Match Types](README.md) {% endhint %} From 02dd5e66dd0f7095e02689ba8e4985a3f2528ffb Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Mon, 17 Aug 2026 15:31:44 +0530 Subject: [PATCH 058/144] Clarify NUMERIC_WITH_UNITS behavior in docs Rewrite and clarify the NUMERIC_WITH_UNITS documentation: emphasize that units only glue to numbers when there is no space, document the special null-handling case, and add an explicit examples table with scores and notes. Expand guidance on mixed glued vs spaced formatting, fix heading/name inconsistencies, tidy Python snippets (imports/formatting), and update the "Read more" link. --- .../match-types/numeric_with_units-match.md | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md index df9000e3d..501c8ab87 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md @@ -1,7 +1,6 @@ --- description: >- - Extracts product codes or numbers with units (16gb, 500ml, 2.4GHz) and - compares how many are the same across both values. Built for product + Extracts product codes or numbers with units and compares how many are the same across both values. Built for product specification fields. --- @@ -9,25 +8,29 @@ description: >- ### What `NUMERIC_WITH_UNITS` does -`NUMERIC_WITH_UNITS` extracts number-and-unit pairs from a string field - "16gb," "500ml", "2.4GHz", and compares how many are the same across two records. It is an extension of NUMERIC that recognises units alongside numbers, making it suitable for product catalogs and specification data where a value without its unit is ambiguous. +`NUMERIC_WITH_UNITS` extracts contiguous alphanumeric tokens that contain a digit - the number and any unit letters glued directly to it, like "16gb" or "2.4GHz" - and compares how many of those tokens are shared across two records. + +**The unit only glues to the number when there's no space between them.** "16gb" extracts as the single token `16gb`, but "16 GB" (with a space) extracts as just `16` - the unit is dropped, not glued. So a number and its unit written with a space in one record and without a space in the other extract to *different* tokens and won't match: `{16gb}` vs `{16}` share nothing. There is no unit-conversion logic either - "500ml" and "0.5L" extract to `{500ml}` and `{0.5L}`, which share nothing, regardless of them being the same physical quantity. + +**Null handling is a third, distinct behavior**, different from both `NUMERIC` and every other match type: if *both* sides extract zero tokens (both null/blank, or both have no digit anywhere), it scores a match (1.0). But if only *one* side extracts zero tokens - including one side being null while the other has real tokens - it scores a non-match (0.0), the same as `NUMERIC`. ### What `NUMERIC_WITH_UNITS` matches and what it does not -
Value AValue BMatch?
16gb RAM16 GB MemoryYes - 16 GB extracted from both
16gb RAM32gb RAMNo - different numbers
500ml0.5LConfirm with team —
unit normalisation (ml vs L)
2.4GHz Dual Core2.4GHz ProcessorYes - 2.4GHz matches
16gb16Confirm - with vs without unit
+
Value AValue BScoreNotes
16gb RAM16 GB Memory0.0 (No){16gb} vs {16} - the space in "16 GB" stops the unit from gluing to the number, so they extract as different tokens
16gb RAM32gb RAM0.0 (No){16gb} vs {32gb} share nothing
500ml0.5L0.0 (No){500ml} vs {0.5L} share nothing; there is no unit conversion
2.4GHz Dual Core2.4GHz Processor1.0 (Yes)2.4GHz extracted from both - same glued format on both sides
16gb160.0 (No){16gb} vs {16} share nothing
[null][null]1.0 (Yes)Both sides extract zero tokens - the auto-match case unique to this match type
[null]16gb RAM0.0 (No)Only one side extracts zero tokens - falls back to a non-match, same as NUMERIC
-### When to use `NUMERIC_WITH_UNIT` +### When to use `NUMERIC_WITH_UNITS`
Product specification fields -Any field containing product specifications with units - storage capacity, screen size, processor speed, volume, weight. E-commerce product catalogs, manufacturing BOMs, medical device specifications. +Any field containing product specifications with units - storage capacity, screen size, processor speed, volume, weight. E-commerce product catalogs, medical device specifications - as long as the number and unit are written glued together (no space) consistently in your data. -"16GB RAM, 512GB SSD" and "16 GB RAM, 512 GB SSD" represent the same configuration. `NUMERIC_WITH_UNIT` handles this. +"16GB RAM, 512GB SSD" and "8GB RAM, 512GB SSD" share the token "512GB" and score a partial match on that overlap. `NUMERIC_WITH_UNITS` works here because the unit is glued to the number the same way in both records.
-### When not to use `NUMERIC_WITH_UNIT` +### When not to use `NUMERIC_WITH_UNITS`
@@ -37,6 +40,14 @@ Use `NUMERIC` for address fields. "42 Main Street" does not have units. `NUMERIC
+
+ +When your data mixes glued and spaced number-unit formatting + +"16GB RAM" and "16 GB RAM" do not match - the space in "16 GB" prevents the unit from gluing to the number, so they extract to different tokens (`16GB` vs `16`) with nothing in common. `NUMERIC_WITH_UNITS` does not normalise this, and there is no unit-conversion logic ("500ml" vs "0.5L" also won't match). If your source systems format this inconsistently, normalise the spacing (and units, if needed) upstream before matching. + +
+ {% tabs %} {% tab title="Python" %} ### **Community** @@ -44,15 +55,16 @@ Use `NUMERIC` for address fields. "42 Main Street" does not have units. `NUMERIC ```python from zingg.client import * - weight = FieldDefinition("weight", "string", MatchType.NUMERIC_WITH_UNITS) +weight = FieldDefinition("weight", "string", MatchType.NUMERIC_WITH_UNITS) ``` ### **Enterprise** ```python from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition +from zingg.client import * - weight = EFieldDefinition("weight", "string", MatchType.NUMERIC_WITH_UNITS) +weight = EFieldDefinition("weight", "string", MatchType.NUMERIC_WITH_UNITS) ``` {% endtab %} @@ -79,7 +91,7 @@ The JSON `fieldDefinition` block is identical for Community and Enterprise. Only **Related types**: * `NUMERIC` - for numbers without unit -* `TEXT` - combine for the descriptive parts of product fields +* `TEXT` - for the descriptive parts of product fields -**Read more**: [Match types](./) +**Read more**: [Match types](README.md) {% endhint %} From 7c04ae25a8ecbd69a7f6b7d61826f1dce7f61aa0 Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Mon, 17 Aug 2026 16:22:12 +0530 Subject: [PATCH 059/144] Docs: Clarify TEXT match behavior and examples Improve docs/docs/zingg-concepts/how-zingg-learns/match-types/text-match.md: clarify that TEXT returns a continuous similarity score in [0,1], tokens are lowercased (not stemmed), and null/blank auto-match as 1.0. Replace the example table with explicit scores and notes. Clean up Python snippets/imports for Community and Enterprise examples, reword related-types bullet, and update the "Read more" link to README.md. These changes make TEXT semantics and examples explicit and reduce ambiguity for integrators. --- .../match-types/text-match.md | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/text-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/text-match.md index 9e850c98d..688fe0d50 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/text-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/text-match.md @@ -9,13 +9,15 @@ description: >- ### What `TEXT` does -`TEXT` compares two string values by measuring the overlap of words between them, how many words appear in both. It is designed for longer descriptive fields like product descriptions, notes, professional bios, or service descriptions where the overall word content matters more than exact character matches. +`TEXT` compares two string values by measuring the overlap of words between them, how many words appear in both. It is designed for longer descriptive fields like product descriptions, notes, professional bios, or service descriptions where the overall word content matters more than exact character matches. The score is a continuous similarity value in \[0, 1]. -`TEXT` works best when your text fields do not have many typos - the comparison is at word level, not character level, so it will not catch spelling errors within words. For shorter name and address fields, use `FUZZY` instead. +`TEXT` works best when your text fields do not have many typos - the comparison is at word level, not character level, tokens are lowercased but not stemmed, so it will not catch spelling errors or inflections ("model" vs "models") within words. For shorter name and address fields, use `FUZZY` instead. + +A null or blank value on either side is an automatic match (1.0). ### What `TEXT` matches and what it does not -
Value AValue BMatch?
Enterprise data management platformData management platform for enterpriseYes - high word overlap
("enterprise", "data", "management",
"platform" all shared)
Enterprise software solutionsConsumer hardware productsNo - low word overlap
Machine learning model trainingTraining machine learning modelsYes - same words, different order
ML modelMachine learning modelPartial - "model" shared,
"ML" vs "Machine learning" differs. Score reflects partial overlap.
[empty]Enterprise data platformConfirm with team —
empty string behaviour
+
Value AValue BScoreNotes
Enterprise data management platformData management platform for enterprise0.8 (Yes)4 of 5 distinct words shared - only "for" is unmatched
Enterprise software solutionsConsumer hardware products0.0 (No)No shared words
Machine learning model trainingTraining machine learning model1.0 (Yes)Same four words, different order - word overlap is order-independent
ML modelMachine learning model0.25 (Partial)Only "model" is shared out of 4 distinct words across both - "ML" and "machine learning" don't overlap at the word level
[null]Enterprise data platform1.0 (Yes)Null/blank on either side auto-matches
[empty string]Enterprise data platform1.0 (Yes)Empty string is treated exactly like null
### When to use `TEXT` @@ -25,8 +27,6 @@ description: >- E-commerce product titles and descriptions often contain the same information in different word orders or phrasing. "16GB RAM Laptop with SSD" and "Laptop SSD 16GB RAM" are the same product. `TEXT` captures word-level similarity across these variations. -Combine with `NUMERIC_WITH_UNITS` for fields containing both descriptive text and numeric specifications. -
@@ -66,14 +66,17 @@ If your free-text fields have typos and spelling errors, `FUZZY` handles those b ```python from zingg.client import * - description = FieldDefinition("description", "string", MatchType.TEXT) + +description = FieldDefinition("description", "string", MatchType.TEXT) ``` ### **Enterprise** ```python from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition - description = EFieldDefinition("description", "string", MatchType.TEXT) +from zingg.client import * + +description = EFieldDefinition("description", "string", MatchType.TEXT) ``` {% endtab %} @@ -100,7 +103,7 @@ The JSON `fieldDefinition` block is identical for Community and Enterprise. Only **Related types:** * `FUZZY` - better for short fields and when typos are present -* `NUMERIC_WITH_UNITS` - combine for product description fields +* `NUMERIC_WITH_UNITS` - Use for product description fields -**Read more**: [Match Types](./) +**Read more**: [Match Types](README.md) {% endhint %} From 911998f2950c7272e9fd348aff9a815cad35c906 Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Mon, 17 Aug 2026 16:49:01 +0530 Subject: [PATCH 060/144] docs: clarify PINCODE match semantics Clarify PINCODE behavior: document that values are truncated at the first hyphen and compared for exact equality (models US ZIP+4), and that null/blank auto-match. Warn that this is not a general international postal-code normalizer and note failure cases (e.g., Polish hyphenated codes). Update example table and add a details section for hyphenated formats where both halves matter. Also tidy example imports and fix the README link. --- .../match-types/pincode-match.md | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md index 3605e5016..40bdf5060 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md @@ -9,13 +9,15 @@ description: >- ### What `PINCODE` does -`PINCODE` is built specifically for postal and PIN code fields. It handles the format variants that postal codes commonly appear in across source systems; a 5-digit US ZIP code and its 9-digit ZIP+4 equivalent. For example, without the tolerance that `FUZZY` would add. +`PINCODE` splits a value on the first hyphen, keeps only the part before it, and compares that prefix for exact equality. It is not a fuzziness dial sitting between `EXACT` and `FUZZY` - it's exact-match on a preprocessed string, where the preprocessing is "truncate at the first hyphen." Concretely, this models the **US ZIP+4 format specifically**: "12345-6789" is compared as just "12345", since the "-6789" is a discardable delivery-route extension in that format. -It is more permissive than `EXACT` (which would not match "94102" and "94102-1234") and more precise than `FUZZY` (which would score "94102" and "94103" as similar). +A null or blank value on either side is an automatic match (1.0). + +**This is not a general international postal-code normalizer.** The hyphen-truncation logic assumes the part after the hyphen is always a discardable suffix. That's true for US ZIP+4, but many countries use a hyphen as a meaningful separator where *both* halves matter - for example, Polish postal codes ("00-950" vs "00-123") or Portuguese ones. On those formats, `PINCODE` truncates both sides down to "00" and reports a match, silently collapsing two genuinely different postal codes. See the table and "When not to use" below. ### What `PINCODE` matches and what it does not -
Value AValue BMatch?
9410294102-1234Yes - 5-digit and ZIP+4 same base
9410294103No - different postal codes
EC1A 1BBEC1A1BBConfirm with team —
UK postcode with/without space
110001110001Yes - Indian PIN code, identical
94102941-02Confirm with team —
hyphen in different position
[null]94102Confirm — add NULL_OR_BLANK to control null behaviour
+
Value AValue BMatch?
9410294102-1234Yes - both truncate to "94102"
9410294103No - "94102" vs "94103"
EC1A 1BBEC1A1BBNo - neither string has a hyphen, so both pass through unsplit
110001110001Yes - Indian PIN code, identical
94102941-02No - "94102" vs "941" ("941-02" truncates to "941", discarding "02")
00-95000-123Yes - a false match. Both truncate to "00"; this is the Polish-postal-code failure case described above, not a US ZIP+4
[null]94102Yes - null/blank on either side auto-matches
### When to use `PINCODE` @@ -39,6 +41,16 @@ This is the only match type specifically designed for postal codes
+
+ +Hyphenated postal codes where both halves are significant + +`PINCODE`'s hyphen handling is US-ZIP+4-specific: it assumes anything after the first hyphen is a discardable suffix. Many countries' formats don't work that way - Polish postal codes ("00-950" vs "00-123") use the hyphen as a meaningful internal separator, and `PINCODE` would truncate both down to "00" and report a false match. + +If your postal code format uses a hyphen where both sides carry meaning, use `EXACT` instead, or normalise the format upstream before matching. + +
+ {% tabs %} {% tab title="Python" %} ### **Community** @@ -46,15 +58,16 @@ This is the only match type specifically designed for postal codes ```python from zingg.client import * - pincode = FieldDefinition("pincode", "string", MatchType.PINCODE) +pincode = FieldDefinition("pincode", "string", MatchType.PINCODE) ``` ### **Enterprise** ```python from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition +from zingg.client import * - pincode = EFieldDefinition("pincode", "string", MatchType.PINCODE) +pincode = EFieldDefinition("pincode", "string", MatchType.PINCODE) ``` {% endtab %} @@ -84,5 +97,5 @@ The JSON `fieldDefinition` block is identical for Community and Enterprise. Only * `NUMERIC` - for other numeric identifier fields * `NULL_OR_BLANK` - combine when postal codes are often missing -**Read more:** [Match Types](./) +**Read more:** [Match Types](README.md) {% endhint %} From ad46ca7e94dc75cdcf0869886d28329604bebe2b Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Mon, 17 Aug 2026 11:37:58 +0000 Subject: [PATCH 061/144] GITBOOK-340: No subject --- docs/zingg-concepts/how-zingg-learns/README.md | 2 +- .../deterministic-vs-probabilistic-matching.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/zingg-concepts/how-zingg-learns/README.md b/docs/zingg-concepts/how-zingg-learns/README.md index 92af98992..dd98a87f4 100644 --- a/docs/zingg-concepts/how-zingg-learns/README.md +++ b/docs/zingg-concepts/how-zingg-learns/README.md @@ -20,7 +20,7 @@ Zingg learns specific ML models from your data, calibrated to your specific fiel Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem.\ And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. -
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.

This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.

Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always break

Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.

A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.

Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.

The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.

The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.

Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.

Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely.

Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

The data drift problem - why a model trained once decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.

Rule-based systems have no mechanism to detect or adapt to these changes. You rewrite the rules manually when performance degrades, which means that someone has to notice the degradation first.

Zingg Enterprise's incremental flow detects when new records do not fit existing clusters cleanly and surfaces them for review. The compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live.

The incremental new data problem

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.

First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.

Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

+
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.

This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.

Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always break

Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.

A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.

Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.

The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.

The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.

Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.

Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely.

Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

The incremental new data problem

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.

First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.

Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

The data drift problem - why a model trained once decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.

Most systems have no mechanism to detect or adapt to these changes.

Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live.

Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.

Zingg starts by scanning your dataset and selecting the most informative candidate pairs, edge cases, near misses, and ambiguous records where human judgment matters most. It does not ask you to label random samples. It is highly selective about which pairs it surfaces, choosing the ones that will teach it the most with the least labeling effort. diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md b/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md index 7ea385990..c2a025901 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md @@ -1,6 +1,6 @@ --- description: >- - How Zingg's two matching approaches work individually and colelctively, when + How Zingg's two matching approaches work individually and collectively, when to use each tags: - ent From 719fdda896204bd8abe67bdedbb6469ea49adc22 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Mon, 17 Aug 2026 11:42:03 +0000 Subject: [PATCH 062/144] GITBOOK-341: No subject --- docs/SUMMARY.md | 1 + docs/zingg-concepts/how-zingg-learns/README.md | 7 ------- ...o-solve-and-why-they-are-harder-than-it-looks.md | 13 +++++++++++++ 3 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 7f34f5455..bf00a603f 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -6,6 +6,7 @@ * [Entity Resolution](zingg-concepts/entity-resolution/README.md) * [Rule based entity resolution](zingg-concepts/entity-resolution/rule-based-entity-resolution.md) * [Zingg Entity Resolution Platform](zingg-concepts/zingg-entity-resolution-platform/README.md) + * [The problems Zingg is built to solve and why they are harder than it looks](zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md) * [Deterministic vs Probabilistic Matching](zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) * [Deduplication, linking and resolving](zingg-concepts/zingg-entity-resolution-platform/deduplication-linking-and-resolving.md) * [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) diff --git a/docs/zingg-concepts/how-zingg-learns/README.md b/docs/zingg-concepts/how-zingg-learns/README.md index dd98a87f4..706df0d8f 100644 --- a/docs/zingg-concepts/how-zingg-learns/README.md +++ b/docs/zingg-concepts/how-zingg-learns/README.md @@ -15,13 +15,6 @@ description: >- Zingg learns specific ML models from your data, calibrated to your specific field distributions, your specific variations, and your specific understanding of what a match means. -#### The problems Zingg is built to solve and why they are harder than it looks - -Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem.\ -And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. - -
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.

This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.

Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always break

Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.

A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.

Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.

The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.

The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.

Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.

Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely.

Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

The incremental new data problem

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.

First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.

Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

The data drift problem - why a model trained once decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.

Most systems have no mechanism to detect or adapt to these changes.

Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live.

Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.

- Zingg starts by scanning your dataset and selecting the most informative candidate pairs, edge cases, near misses, and ambiguous records where human judgment matters most. It does not ask you to label random samples. It is highly selective about which pairs it surfaces, choosing the ones that will teach it the most with the least labeling effort. You label those pairs typically 30 to 50, as Match, No Match, or Uncertain. From those labels, Zingg builds two models: a blocking model that cuts down the comparison space to a tiny fraction of all possible pairs, and a similarity model that scores each remaining candidate pair with graded confidence. diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md new file mode 100644 index 000000000..b356df6a2 --- /dev/null +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -0,0 +1,13 @@ +# The problems Zingg is built to solve and why they are harder than it looks + +Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem. And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. + +| Problem | Description | +| -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **The N² comparison problem** |

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

| +| **The variation problem - why rules always break** | Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance. | +| **The confidence problem - why binary match/no-match is not enough** |

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely.Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.Read more:

| +| **The incremental new data problem** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.Read more:

| +| **The data drift problem - why a model trained once decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes.Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

| + +
From 13164de3b2526f5f6d187e9aaacc84258b7e11dd Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Mon, 17 Aug 2026 11:56:50 +0000 Subject: [PATCH 063/144] GITBOOK-342: No subject --- docs/SUMMARY.md | 4 +- docs/running-zingg/verify-blocking.md | 92 ++++++++++++++++++- .../how-zingg-learns/zingg-models/README.md | 24 +---- .../zingg-models/blocking-model.md | 92 +++---------------- 4 files changed, 106 insertions(+), 106 deletions(-) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index bf00a603f..632bb0e71 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -9,8 +9,6 @@ * [The problems Zingg is built to solve and why they are harder than it looks](zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md) * [Deterministic vs Probabilistic Matching](zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) * [Deduplication, linking and resolving](zingg-concepts/zingg-entity-resolution-platform/deduplication-linking-and-resolving.md) -* [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) - * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) * [How Zingg Learns](zingg-concepts/how-zingg-learns/README.md) * [Zingg Active Learning](zingg-concepts/how-zingg-learns/zingg-active-learning.md) * [Zingg Models](zingg-concepts/how-zingg-learns/zingg-models/README.md) @@ -36,6 +34,8 @@ * [Identity Graph](zingg-concepts/identity-graph.md) * [Z Cluster and Zingg ID](zingg-concepts/z-cluster-and-zingg-id.md) * [Pass Through](zingg-concepts/pass-through.md) +* [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) + * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) * [Concept Glossary](zingg-concepts/concept-glossary.md) ## Running Zingg diff --git a/docs/running-zingg/verify-blocking.md b/docs/running-zingg/verify-blocking.md index fb8f23084..42284c1d8 100644 --- a/docs/running-zingg/verify-blocking.md +++ b/docs/running-zingg/verify-blocking.md @@ -2,13 +2,102 @@ description: >- Verify that your blocking model is grouping known matching pairs into the same block before training and after. +tags: + - enterprise-only --- # Verify Blocking The blocking model filters your dataset before any similarity comparisons happen. Records in different blocks are never compared. This is what makes Zingg scalable, but it also means that if the blocking model puts two matching records in different blocks, those records will never be matched no matter how well the similarity model is trained. -`verifyBlocking` tells you what percentage of your known matching pairs are being blocked together correctly. Run this test after configuring Zingg and again after training if you suspect missed matches. +`verifyBlocking` tells you what percentage of your known matching pairs are being blocked together correctly. Run this test after configuring Zingg and again after training if you suspect missed matches.\ +
+ +TODO - move to verify blocking + +How to inspect the blocking model, understand its coverage, and fix missed matches caused by blocking. + +The blocking model is the first filter in every Zingg run. It decides which record pairs the similarity model ever sees. If it places two matching records in different buckets, those records will never be compared, and the match will be missed regardless of how well everything else is configured. + +If your results are missing matches you expect to see, start here. + +### **What `verifyBlocking` output contains** + +The `verifyBlocking` phase produces two output directories under `zinggDir/modelId/blocks/timestamp/`: + +| Output path | What it contains | +| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `counts` | Record count per block. Shows how many records are in each block. Very large blocks indicate poor blocking specificity. | +| `blockSamples` | The top 10% of records associated with each block. Use this to understand which records are being grouped together and whether the groupings make sense. | + +{% hint style="success" icon="right-long" %} +**Read more**: To run the `verifyBlocking` phase and inspect output coverage→ [Verify Blocking](verify-blocking.md) +{% endhint %} + +### Why blocking coverage fails and how to fix it + +Poor blocking coverage means matching pairs are being placed in different buckets and never compared. Four causes account for most cases: + +
+ +Skewed or limited training samples + +The blocking model learns heuristics from your labeled training data. If your training data is skewed; for example, all your labeled pairs are from a single state, city, or category the blocking model learns blocking rules that are too narrow for the full dataset. + +**Fix**: Run `findTrainingData` again and label pairs that represent the full distribution of your data. If your dataset has records from 50 states, your training data should include\ +pairs from multiple states not just the largest. + +
+ +
+ +Manually added training samples overriding learned rules + +If you supplement your training data with manually created pairs rather than pairs selected by `findTrainingData` those pairs, it may teach the blocking model rules that are too specific to those exact examples. The blocking model needs representative pairs selected by Zingg's own candidate selection logic, not hand-curated ones. + +**Fix**: Use `findTrainingData` to generate candidate pairs. Add manually created training data only to supplement coverage gaps, and ensure they are diverse enough that the blocking model can generalize from them. + +
+ +
+ +Too few training examples + +30 to 50 labeled `match` pairs is a good starting point for the similarity model. But the blocking model may need more examples, especially on large datasets with many field variations; before it learns blocking rules that are generic enough to cover the full problem space. + +**Fix**: Run `findTrainingData` and `label` additional round. Pay particular attention to `match` pairs that cover different field value patterns different name formats, address abbreviations, and missing field combinations. + +
+ +
+ +Non-differentiating columns in the schema + +If your field definitions include columns that are the same or nearly the same across a large proportion of your records, for example, a country code that is "US" for 99%\ +of records those columns produce very large blocks. Large blocks slow down matching and indicate that the blocking model is not creating useful partitions. + +**Fix**: Mark non-differentiating columns as `DONT_USE` in your field definitions. `DONT_USE` fields are excluded from both blocking and similarity; they appear in output but do not\ +influence matching. + +
+ +### If `verifyBlocking` shows low coverage, what to do in order + +1. Add more labeled training pairs of the type being missed. The blocking model learns from the same training data as the similarity model. Focus on pairs that represent the variation patterns missing from your current training set. +2. Review your field match types. Fields marked `DONT_US`E are excluded from blocking as well as similarity. If a field has a strong identity signal - a consistent identifier that appears across matching records - consider changing it from `DONT_USE` to `FUZZY`. +3. Check whether missed pairs share a common characteristic. If all missed pairs have empty values in a key field, empty fields cannot contribute to blocking. Either remove that field from your blocking config or improve data completeness upstream. +4. If standard blocking consistently misses a specific pattern in your data, consider custom blocking functions. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* [Label Training Pairs](label-training-pairs.md) - how to add more training data +* [Configure Zingg](configure-zingg.md) - changing field match types +* [Blocking Strategies: DEFAULT vs WIDER](../tuning/blocking-strategy.md) - reorder candidate fields in the blocking tree (Enterprise only) +* [Custom Blocking and Similarity](../tuning/custom-blocking-and-similarity.md) Functions +{% endhint %} + + {% hint style="success" icon="right-long" %} Run `verifyBlocking` in two situations: @@ -59,7 +148,6 @@ zingg.initAndExecute() {% endtab %} {% tab title="Enterprise Snowflake" %} - ### CLI ```bash diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md index 7f0989233..85167e6dd 100644 --- a/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md @@ -12,28 +12,7 @@ When you run the train phase, Zingg builds two separate machine learning models Understanding what each model does gives you a clear framework for diagnosing problems, tuning performance, and knowing which part of the pipeline to adjust when results are not what\ you expect. -### The blocking model - solving the scale problem - -The fundamental challenge of entity resolution at scale is not accuracy. It is the size of the comparison space. - -Comparing every record against every other record produces `N × (N-1) / 2` comparisons, a number that grows quadratically with dataset size. At one million records, that is 500 billion comparisons. At ten million records, it is 50 trillion. No system can evaluate 50 trillion pairs at production cadence. - -The blocking model is Zingg's solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using field heuristics it learns from your training data. Only records within the same bucket are ever compared against each other. Records in different buckets are never compared, which means the blocking model is the first and most\ -consequential filter in the pipeline. - -Typical Zingg blocking reduces the comparison space to 0.05% to 1% of all possible pairs, without losing the pairs that matter. - -The cost of overly aggressive blocking is that it leads to missed matches. If the blocking model places two matching records into different buckets, the similarity model will never compare them. That pair will not appear in your output regardless of how well the similarity model is\ -trained. - -This is why blocking is the first thing to verify when matches you expect are missing. - -{% hint style="success" icon="right-long" %} -The blocking model learns from your labeled training pairs, the same training data that teaches the\ -similarity model. Better training data improves both models. - -For diagnosing blocking coverage and concept details → [Blocking Model](blocking-model.md) -{% endhint %} +### ### The similarity model - solving the accuracy problem @@ -84,4 +63,3 @@ If you retrain with a new model, use `Compare Model Results (diff phase)` in Ent * [Compare Model Results](../../../running-zingg/compare-model-results.md) - benchmark two models before deploying * [Reassign Zingg ID](../../../running-zingg/reassign-zingg-id.md) - carry existing IDs to a new model {% endhint %} - diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md index 03dc57ce3..46c15369f 100644 --- a/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md @@ -1,91 +1,25 @@ -# Blocking Model - -When your data contains reliable unique identifiers, a national ID, an email, or a combination like first name plus date of birth - - - -TODO - move to verify blocking - -How to inspect the blocking model, understand its coverage, and fix missed matches caused by blocking. - - - -The blocking model is the first filter in every Zingg run. It decides which record pairs the similarity model ever sees. If it places two matching records in different buckets, those records will never be compared, and the match will be missed regardless of how well everything else is configured. - -If your results are missing matches you expect to see, start here. - -### **What `verifyBlocking` output contains** - -The `verifyBlocking` phase produces two output directories under `zinggDir/modelId/blocks/timestamp/`: - -| Output path | What it contains | -| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `counts` | Record count per block. Shows how many records are in each block. Very large blocks indicate poor blocking specificity. | -| `blockSamples` | The top 10% of records associated with each block. Use this to understand which records are being grouped together and whether the groupings make sense. | - -{% hint style="success" icon="right-long" %} -**Read more**: To run the `verifyBlocking` phase and inspect output coverage→ [Verify Blocking](../../../running-zingg/verify-blocking.md) -{% endhint %} - -### Why blocking coverage fails and how to fix it +--- +description: Solving the scale problem +--- -Poor blocking coverage means matching pairs are being placed in different buckets and never compared. Four causes account for most cases: - -
- -Skewed or limited training samples - -The blocking model learns heuristics from your labeled training data. If your training data is skewed; for example, all your labeled pairs are from a single state, city, or category the blocking model learns blocking rules that are too narrow for the full dataset. - -**Fix**: Run `findTrainingData` again and label pairs that represent the full distribution of your data. If your dataset has records from 50 states, your training data should include\ -pairs from multiple states not just the largest. - -
- -
- -Manually added training samples overriding learned rules - -If you supplement your training data with manually created pairs rather than pairs selected by `findTrainingData` those pairs, it may teach the blocking model rules that are too specific to those exact examples. The blocking model needs representative pairs selected by Zingg's own candidate selection logic, not hand-curated ones. - -**Fix**: Use `findTrainingData` to generate candidate pairs. Add manually created training data only to supplement coverage gaps, and ensure they are diverse enough that the blocking model can generalize from them. - -
- -
- -Too few training examples - -30 to 50 labeled `match` pairs is a good starting point for the similarity model. But the blocking model may need more examples, especially on large datasets with many field variations; before it learns blocking rules that are generic enough to cover the full problem space. - -**Fix**: Run `findTrainingData` and `label` additional round. Pay particular attention to `match` pairs that cover different field value patterns different name formats, address abbreviations, and missing field combinations. - -
+# Blocking Model -
+The fundamental challenge of entity resolution at scale is not accuracy. It is the size of the comparison space. -Non-differentiating columns in the schema +Comparing every record against every other record produces `N × (N-1) / 2` comparisons, a number that grows quadratically with dataset size. At one million records, that is 500 billion comparisons. At ten million records, it is 50 trillion. No system can evaluate 50 trillion pairs at production cadence. -If your field definitions include columns that are the same or nearly the same across a large proportion of your records, for example, a country code that is "US" for 99%\ -of records those columns produce very large blocks. Large blocks slow down matching and indicate that the blocking model is not creating useful partitions. +The blocking model is Zingg's approach to making entity resolution scalable. Before any similarity comparison runs, the blocking model groups records into candidate buckets using field heuristics it learns from your training data. This approach finds the best possible reduction in comparisons completely tailored to your data, while ensuring results are highly accurate. Typical Zingg blocking reduces the comparison space to 0.05% to 1% of all possible pairs, without losing the pairs that matter.\ +\ +Only records within the same bucket are ever compared against each other. Records in different buckets are never compared, which means the blocking model is the first and most\ +consequential filter in the pipeline. -**Fix**: Mark non-differentiating columns as `DONT_USE` in your field definitions. `DONT_USE` fields are excluded from both blocking and similarity; they appear in output but do not\ -influence matching. -
-### If `verifyBlocking` shows low coverage, what to do in order -1. Add more labeled training pairs of the type being missed. The blocking model learns from the same training data as the similarity model. Focus on pairs that represent the variation patterns missing from your current training set. -2. Review your field match types. Fields marked `DONT_US`E are excluded from blocking as well as similarity. If a field has a strong identity signal - a consistent identifier that appears across matching records - consider changing it from `DONT_USE` to `FUZZY`. -3. Check whether missed pairs share a common characteristic. If all missed pairs have empty values in a key field, empty fields cannot contribute to blocking. Either remove that field from your blocking config or improve data completeness upstream. -4. If standard blocking consistently misses a specific pattern in your data, consider custom blocking functions. {% hint style="success" icon="right-long" %} -**Read more**: +The blocking model learns from your labeled training pairs, the same training data that teaches the\ +similarity model. Better training data improves both models. -* [Label Training Pairs](../../../running-zingg/label-training-pairs.md) - how to add more training data -* [Configure Zingg](../../../running-zingg/configure-zingg.md) - changing field match types -* [Blocking Strategies: DEFAULT vs WIDER](../../../tuning/blocking-strategy.md) - reorder candidate fields in the blocking tree (Enterprise only) -* [Custom Blocking and Similarity](../../../tuning/custom-blocking-and-similarity.md) Functions +For diagnosing blocking coverage and concept details → [Blocking Model](blocking-model.md) {% endhint %} From 62fd6e70e19191cda187159fe9c19c8e594056da Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Mon, 17 Aug 2026 12:03:55 +0000 Subject: [PATCH 064/144] GITBOOK-343: No subject --- .../how-zingg-learns/zingg-models/blocking-model.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md index 46c15369f..cfa004d98 100644 --- a/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md @@ -1,5 +1,5 @@ --- -description: Solving the scale problem +description: Solving the scale problem by learning from the data directly --- # Blocking Model From 44bb70070be184b88ef4d5f939f2e80b728478b4 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Mon, 17 Aug 2026 12:11:21 +0000 Subject: [PATCH 065/144] GITBOOK-344: No subject --- docs/SUMMARY.md | 2 +- .../tuning/configure-field-standardization.md | 2 +- .../zingg-models/blocking-model.md | 32 +++++++++++++++++++ .../standardize-fields-and-results.md | 6 ++-- 4 files changed, 37 insertions(+), 5 deletions(-) rename docs/zingg-concepts/{how-zingg-learns/zingg-models => }/standardize-fields-and-results.md (93%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 632bb0e71..adf55521f 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -14,7 +14,6 @@ * [Zingg Models](zingg-concepts/how-zingg-learns/zingg-models/README.md) * [Blocking Model](zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) * [Similarity Model](zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md) - * [Standardize Fields and Results](zingg-concepts/how-zingg-learns/zingg-models/standardize-fields-and-results.md) * [Match Types](zingg-concepts/how-zingg-learns/match-types/README.md) * [FUZZY Match](zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md) * [FUZZY\_OPTIMISED Match](zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md) @@ -31,6 +30,7 @@ * [NULL\_OR\_BLANK Match](zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md) * [DONT\_USE Match](zingg-concepts/how-zingg-learns/match-types/dont_use-match.md) * [MAPPING\_(FILENAME) Match](zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md) +* [Standardize Fields and Results](zingg-concepts/standardize-fields-and-results.md) * [Identity Graph](zingg-concepts/identity-graph.md) * [Z Cluster and Zingg ID](zingg-concepts/z-cluster-and-zingg-id.md) * [Pass Through](zingg-concepts/pass-through.md) diff --git a/docs/tuning/configure-field-standardization.md b/docs/tuning/configure-field-standardization.md index 953de9741..dd0dffbe0 100644 --- a/docs/tuning/configure-field-standardization.md +++ b/docs/tuning/configure-field-standardization.md @@ -129,7 +129,7 @@ If the output still shows raw variants: {% hint style="success" icon="right-long" %} **Read more**: -* [Standardize Fields and Results](../zingg-concepts/how-zingg-learns/zingg-models/standardize-fields-and-results.md) - concept and where it is useful +* [Standardize Fields and Results](../zingg-concepts/standardize-fields-and-results.md) - concept and where it is useful * [Configure Zingg](../running-zingg/configure-zingg.md) - full field definition configuration including all `EFieldDefinition` methods * [Match Types](../zingg-concepts/how-zingg-learns/match-types/) - for the MAPPING match type, which uses a similar mapping file approach for matching {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md index cfa004d98..f4f396268 100644 --- a/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md @@ -13,7 +13,39 @@ The blocking model is Zingg's approach to making entity resolution scalable. Bef Only records within the same bucket are ever compared against each other. Records in different buckets are never compared, which means the blocking model is the first and most\ consequential filter in the pipeline. +### Blocking functions +The blocking model is a purpose built entity resolution model, comprising of a tree with hash functions. Blocking functions control which records are compared. The blocking tree learns on the matched records you provided during labeling; at every node, Zingg selects the function and the field that produces the least elimination of your known matching pairs. + +**What makes a good blocking function:** + +A good blocking function never eliminates a matching pair entirely. It groups records that could be the same entity, even imperfectly; so the similarity model can evaluate them. A poor blocking function eliminates matching pairs from comparison entirely; no similarity scoring happens after that point. + +**Example: evaluating `first1char` on `firstname`** + +Take two labeled matching pairs: + +| 1 | A | john | j | +| - | - | ----- | - | +| 1 | B | johnh | j | +| 2 | A | mary | m | +| 2 | B | marry | m | + +Both pairs produce the same output from `first1char` - no elimination. This is a good function for `firstname`. + +**Contrast: `last1char` on `firstname`** + +| **1** | A | john | n | +| ----- | - | ----- | - | +| 1 | B | johnh | h | +| 1 | A | mary | y | +| 2 | B | marry | y | + +Pair 1 is eliminated (`n` ≠ `h`). `last1char` is not a good function for `firstname`. Zingg will therefore not choose it. + +So `first1char(firstname)` will be selected. It brings near-similar records together - clustering them to break the cartesian join. + +The good part is that the user does not have to think about these constructs at all. During active learning, the model is automatically learnt based on what the user labels. diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/standardize-fields-and-results.md b/docs/zingg-concepts/standardize-fields-and-results.md similarity index 93% rename from docs/zingg-concepts/how-zingg-learns/zingg-models/standardize-fields-and-results.md rename to docs/zingg-concepts/standardize-fields-and-results.md index 741d91777..b8b14d15d 100644 --- a/docs/zingg-concepts/how-zingg-learns/zingg-models/standardize-fields-and-results.md +++ b/docs/zingg-concepts/standardize-fields-and-results.md @@ -60,7 +60,7 @@ Enforce canonical values on classification fields like industry codes, status la {% hint style="success" icon="right-long" %} **Read more**: -* To create a mapping file and configure the postprocessor on a field → [Configure Field Standardization](../../../tuning/configure-field-standardization.md) -* For the full field definition configuration including all `EFieldDefinition` methods - [Configure Zingg](../../../running-zingg/configure-zingg.md) -* For dictionary-based matching using the MAPPING match type (nicknames, aliases, company names) - [Match Types](../match-types/) +* To create a mapping file and configure the postprocessor on a field → [Configure Field Standardization](../tuning/configure-field-standardization.md) +* For the full field definition configuration including all `EFieldDefinition` methods - [Configure Zingg](../running-zingg/configure-zingg.md) +* For dictionary-based matching using the MAPPING match type (nicknames, aliases, company names) - [Match Types](how-zingg-learns/match-types/) {% endhint %} From 2887076b550ee8137bd74331e415f93988d7862d Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Mon, 17 Aug 2026 17:49:06 +0530 Subject: [PATCH 066/144] Add Update Label doc Add a new docs/running-zingg/update-label.md that documents the updateLabel phase (interactive relabeling by cluster ID), includes Python/CLI examples, backup warning, and post-update guidance. Update docs/SUMMARY.md to link the new page. Simplify docs/running-zingg/label-training-pairs.md by removing the inline updateLabel section and adding a hint to the new page, plus a couple small wording cleanups. Also apply a minor wording/table tweak in the pincode-match.md file. --- docs/SUMMARY.md | 1 + docs/running-zingg/label-training-pairs.md | 29 +++---------- docs/running-zingg/update-label.md | 42 +++++++++++++++++++ .../match-types/pincode-match.md | 4 +- 4 files changed, 50 insertions(+), 26 deletions(-) create mode 100644 docs/running-zingg/update-label.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index adf55521f..d5dcad02a 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -49,6 +49,7 @@ * [Label Training Pairs](running-zingg/label-training-pairs.md) * [Verify Blocking](running-zingg/verify-blocking.md) * [Generate Model Documentation](running-zingg/generate-model-documentation.md) +* [Update Label](running-zingg/update-label.md) * [Build and Save the Model](running-zingg/build-and-save-the-model.md) * [Run the match phase](running-zingg/run-the-match-phase.md) * [Link across Datasets](running-zingg/link-across-datasets.md) diff --git a/docs/running-zingg/label-training-pairs.md b/docs/running-zingg/label-training-pairs.md index 90e545640..eaf21fa48 100644 --- a/docs/running-zingg/label-training-pairs.md +++ b/docs/running-zingg/label-training-pairs.md @@ -16,7 +16,7 @@ If you already have labeled data from an external source, you can supply it dire {% step %} ### Step 1: Label -Review each pair presented. Enter your decision: Match (1), No Match (2), or Can't Say (0). You will see different attribute variations at each stage. +Review each pair presented. Enter your decision: Match (1), No Match (2), or Can't Say (0). {% endstep %} {% step %} @@ -28,13 +28,13 @@ Your labels are saved automatically to `zinggDir/modelId` after each session. {% step %} ### Step 3: Iterate +if the number of matched and unmatched pairs are still not sufficient, then iterate. Run `findTrainingData` again to obtain a fresh set of candidate pairs. Label those. Repeat until Zingg's predictions align with your expectations. {% endstep %} {% endstepper %} {% tabs %} {% tab title="Community" %} -In Community, pairs are presented serially in the terminal. Review each pair and enter your decision. ### **Python** @@ -57,28 +57,9 @@ zingg.initAndExecute() `--showConcise=true` is optional. It only shows fields which are `NOT DONT_USE`, making the labelling session cleaner when you have many fields. {% endhint %} -### `updateLabel` section - -As your understanding of your data evolves, you may need to revisit and correct previously marked pairs. Generate model documentation first (see \[Generate Model Documentation]), then run `updateLabel`: - -#### Python - -```python -options = ClientOptions([ - ClientOptions.PHASE, - "updateLabel" -]) -zingg = Zingg(args, options) -zingg.initAndExecute() -``` - -#### CLI - -```bash -./scripts/zingg.sh --phase updateLabel --conf -``` - -This opens the console labeler, which accepts the cluster ID of the pairs you want to update. Note: Keep a backup of your model folder before running `updateLabel`. +{% hint style="info" icon="right-long" %} +Need to correct a pair you already labeled? See [Update Label](update-label.md). +{% endhint %} {% endtab %} {% tab title="Enterprise" %} diff --git a/docs/running-zingg/update-label.md b/docs/running-zingg/update-label.md new file mode 100644 index 000000000..6f3234c06 --- /dev/null +++ b/docs/running-zingg/update-label.md @@ -0,0 +1,42 @@ +--- +description: >- + Revisit and correct a previously labeled pair by cluster ID, without + re-labeling everything from scratch. +--- + +# Update Label + +{% hint style="warning" icon="right-long" %} +Keep a backup of your model folder before running `updateLabel` - it overwrites previously saved labels. +{% endhint %} + +As your understanding of your data evolves, you may need to correct a pair you labeled earlier. The `updateLabel` phase reopens the console labeler for one cluster at a time, instead of stepping through every candidate pair again. + +Generate model documentation first - see [Generate Model Documentation](generate-model-documentation.md) - so you can look up the cluster ID of the pair you want to correct. + +### Run updateLabel + +#### Python + +```python +options = ClientOptions([ + ClientOptions.PHASE, + "updateLabel" +]) +zingg = Zingg(args, options) +zingg.initAndExecute() +``` + +#### CLI + +```bash +./scripts/zingg.sh --phase updateLabel --conf +``` + +### What happens next + +The command opens an interactive prompt in the terminal. Enter the cluster ID of the pair you want to relabel, review the pair it displays, then enter the corrected decision: Match (1), No Match (2), or Can't Say (0). Enter 9 at either prompt to exit. + +{% hint style="success" icon="right-long" %} +After updating labels, run [Generate Model Documentation](generate-model-documentation.md) again to confirm the change, then re-run `train` once you're satisfied. +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md index 40bdf5060..3ddc510f9 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md @@ -11,13 +11,13 @@ description: >- `PINCODE` splits a value on the first hyphen, keeps only the part before it, and compares that prefix for exact equality. It is not a fuzziness dial sitting between `EXACT` and `FUZZY` - it's exact-match on a preprocessed string, where the preprocessing is "truncate at the first hyphen." Concretely, this models the **US ZIP+4 format specifically**: "12345-6789" is compared as just "12345", since the "-6789" is a discardable delivery-route extension in that format. -A null or blank value on either side is an automatic match (1.0). +A null or blank value on either side is an automatic match. **This is not a general international postal-code normalizer.** The hyphen-truncation logic assumes the part after the hyphen is always a discardable suffix. That's true for US ZIP+4, but many countries use a hyphen as a meaningful separator where *both* halves matter - for example, Polish postal codes ("00-950" vs "00-123") or Portuguese ones. On those formats, `PINCODE` truncates both sides down to "00" and reports a match, silently collapsing two genuinely different postal codes. See the table and "When not to use" below. ### What `PINCODE` matches and what it does not -
Value AValue BMatch?
9410294102-1234Yes - both truncate to "94102"
9410294103No - "94102" vs "94103"
EC1A 1BBEC1A1BBNo - neither string has a hyphen, so both pass through unsplit
110001110001Yes - Indian PIN code, identical
94102941-02No - "94102" vs "941" ("941-02" truncates to "941", discarding "02")
00-95000-123Yes - a false match. Both truncate to "00"; this is the Polish-postal-code failure case described above, not a US ZIP+4
[null]94102Yes - null/blank on either side auto-matches
+
Value AValue BMatch?
9410294102-1234Yes - both truncate to "94102"
9410294103No - "94102" vs "94103"
EC1A 1BBEC1A1BBNo - neither string has a hyphen, so both pass through unsplit
110001110001Yes - Indian PIN code, identical
94102941-02No - "94102" vs "941" ("941-02" truncates to "941", discarding "02")
00-95000-123Yes - a false match. Both truncate to "00"; this is the Polish-postal-code failure case
[null]94102Yes - null/blank on either side auto-matches
### When to use `PINCODE` From 320c1555f953e7e1e9e94dfe2fec989e42b0d19e Mon Sep 17 00:00:00 2001 From: padam Date: Mon, 17 Aug 2026 12:27:44 +0000 Subject: [PATCH 067/144] GITBOOK-346: No subject --- docs/running-zingg/label-training-pairs.md | 34 +++++++++++++--------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/docs/running-zingg/label-training-pairs.md b/docs/running-zingg/label-training-pairs.md index eaf21fa48..a82c3b490 100644 --- a/docs/running-zingg/label-training-pairs.md +++ b/docs/running-zingg/label-training-pairs.md @@ -10,7 +10,7 @@ The `label` phase opens an interactive layer where you review the candidate pair If you already have labeled data from an external source, you can supply it directly using `trainingSamples` in your configuration. See [Create Training Data](create-training-data.md) for how to set this up. -30 to 40 matching pairs is a strong starting point. Label until you feel that your labeled examples represent all field types and data patterns in your schema. If accuracy needs improvement after your first match run, return to labeling—focus on the patterns or field combinations that appear to be missing or underrepresented. +30 to 40 matching pairs is a strong starting point. Label until you feel that your labeled examples represent all field types and data patterns in your schema. If accuracy needs improvement after your first match run, return to labelling, focus on the patterns or field combinations that appear to be missing or underrepresented. {% stepper %} {% step %} @@ -28,14 +28,12 @@ Your labels are saved automatically to `zinggDir/modelId` after each session. {% step %} ### Step 3: Iterate -if the number of matched and unmatched pairs are still not sufficient, then iterate. -Run `findTrainingData` again to obtain a fresh set of candidate pairs. Label those. Repeat until Zingg's predictions align with your expectations. +if the number of matched and unmatched pairs are still not sufficient, then iterate. Run `findTrainingData` again to obtain a fresh set of candidate pairs. Label those. Repeat until Zingg's predictions align with your expectations. {% endstep %} {% endstepper %} {% tabs %} {% tab title="Community" %} - ### **Python** ```python @@ -54,7 +52,7 @@ zingg.initAndExecute() ``` {% hint style="success" icon="right-long" %} -`--showConcise=true` is optional. It only shows fields which are `NOT DONT_USE`, making the labelling session cleaner when you have many fields. +`--showConcise=true` is optional. It only shows fields which are not `DONT_USE`, making the labelling session cleaner when you have many fields. {% endhint %} {% hint style="info" icon="right-long" %} @@ -63,8 +61,6 @@ Need to correct a pair you already labeled? See [Update Label](update-label.md). {% endtab %} {% tab title="Enterprise" %} -`--showConcise=true` is optional. It only shows fields which are `NOT DONT_USE`, making the labelling session cleaner when you have many fields. - #### Python ```python @@ -76,20 +72,32 @@ zingg = EZingg(args, options) zingg.initAndExecute() ``` -{% hint style="info" icon="right-long" %} -Notebook provides a visual widget showing one pair at a time with Match, No Match, and Can't Say buttons. +### **CLI** + +```bash +./scripts/zingg.sh --phase label --conf config.json --showConcise=true +``` + +{% hint style="success" icon="right-long" %} +`--showConcise=true` is optional. It only shows fields which are not `DONT_USE`, making the labelling session cleaner when you have many fields. {% endhint %} +{% hint style="info" icon="right-long" %} +Notebook provides a visual widget showing one pair at a time with Match, No Match, and Can't Say buttons. +{% endhint %} {% endtab %} {% tab title="Enterprise Snowflake" %} - ### Run label Zingg on Snowflake can be run either from a local terminal via the CLI, or natively inside Snowflake using an interactive labeling service. #### CLI (local terminal) -`--showConcise=true` is optional. It only shows fields which are `NOT DONT_USE`, making the labelling session cleaner when you have many fields. + +{% hint style="success" icon="right-long" %} +`--showConcise=true` is optional. It only shows fields which are not `DONT_USE`, making the labelling session cleaner when you have many fields. +{% endhint %} + ```bash ./scripts/zingg.sh --phase label --conf config.json --showConcise=true \ --properties-file @@ -126,7 +134,7 @@ ALTER COMPUTE POOL CONTAINER_ZINGG_POOL STOP ALL; ``` {% hint style="info" icon="right-long" %} -Notebook provides a visual widget showing one pair at a time with Match, No Match, and Can't Say buttons. +Notebook provides a visual widget showing one pair at a time with Match, No Match, and Can't Say buttons. {% endhint %} {% endtab %} -{% endtabs %} \ No newline at end of file +{% endtabs %} From bb0cdf6980234c1e4b326b1ba37b5d9a9dfbaaa2 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Mon, 17 Aug 2026 12:46:35 +0000 Subject: [PATCH 068/144] GITBOOK-347: No subject --- docs/SUMMARY.md | 2 +- ...e-and-why-they-are-harder-than-it-looks.md | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index d5dcad02a..3abef2571 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -6,7 +6,7 @@ * [Entity Resolution](zingg-concepts/entity-resolution/README.md) * [Rule based entity resolution](zingg-concepts/entity-resolution/rule-based-entity-resolution.md) * [Zingg Entity Resolution Platform](zingg-concepts/zingg-entity-resolution-platform/README.md) - * [The problems Zingg is built to solve and why they are harder than it looks](zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md) + * [The problems Zingg is built to solve](zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md) * [Deterministic vs Probabilistic Matching](zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) * [Deduplication, linking and resolving](zingg-concepts/zingg-entity-resolution-platform/deduplication-linking-and-resolving.md) * [How Zingg Learns](zingg-concepts/how-zingg-learns/README.md) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index b356df6a2..39170ca52 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -1,13 +1,17 @@ -# The problems Zingg is built to solve and why they are harder than it looks +--- +description: And why they are harder than it looks +--- + +# The problems Zingg is built to solve Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem. And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. -| Problem | Description | -| -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **The N² comparison problem** |

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

| -| **The variation problem - why rules always break** | Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance. | -| **The confidence problem - why binary match/no-match is not enough** |

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely.Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.Read more:

| -| **The incremental new data problem** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.Read more:

| -| **The data drift problem - why a model trained once decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes.Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

| +| Problem | Description | +| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **The N² comparison problem** |

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

| +| **The variation problem - why rules always break** | Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance. | +| **The confidence problem - why binary match/no-match is not enough** |

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely.Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.Read more:

| +| **The incremental new data problem** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.Read more:

| +| **The data drift problem - why a model trained once decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes.Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

|
From 125a7af0868e3966f0b6112fb8ff9493e6a84d56 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Mon, 17 Aug 2026 12:48:19 +0000 Subject: [PATCH 069/144] GITBOOK-348: No subject --- docs/zingg-concepts/how-zingg-learns/zingg-active-learning.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-active-learning.md b/docs/zingg-concepts/how-zingg-learns/zingg-active-learning.md index 9d43faa23..6885d1b2a 100644 --- a/docs/zingg-concepts/how-zingg-learns/zingg-active-learning.md +++ b/docs/zingg-concepts/how-zingg-learns/zingg-active-learning.md @@ -1,3 +1,7 @@ +--- +description: Yes, no, cant say is all it takes +--- + # Zingg Active Learning ### The active learning loop From 0cf421b23a59acc4dcacd1f78d78ee27fa6bf8fa Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Mon, 17 Aug 2026 13:05:18 +0000 Subject: [PATCH 070/144] GITBOOK-349: No subject --- docs/SUMMARY.md | 5 ++-- .../platform-guide-for-azure-databricks.md | 4 +-- .../platform-guide-for-gcp-dataproc.md | 2 +- .../platform-guide-for-microsoft-fabric.md | 2 +- .../tuning/configure-field-standardization.md | 2 +- docs/zingg-concepts/concept-glossary.md | 2 +- .../how-zingg-learns/zingg-models/README.md | 27 +------------------ .../zingg-models/similarity-model.md | 27 ++++++++++++++++--- .../README.md} | 8 +++--- .../standardize-fields-and-results.md | 6 ++--- docs/zingg-concepts/the-graph-algorithm.md | 21 +++++++++++++++ docs/zingg-concepts/z-cluster-and-zingg-id.md | 2 +- .../README.md | 8 ++---- 13 files changed, 64 insertions(+), 52 deletions(-) rename docs/zingg-concepts/{identity-graph.md => identity-graph/README.md} (92%) rename docs/zingg-concepts/{ => identity-graph}/standardize-fields-and-results.md (92%) create mode 100644 docs/zingg-concepts/the-graph-algorithm.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 3abef2571..7b7edb6e9 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -30,8 +30,9 @@ * [NULL\_OR\_BLANK Match](zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md) * [DONT\_USE Match](zingg-concepts/how-zingg-learns/match-types/dont_use-match.md) * [MAPPING\_(FILENAME) Match](zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md) -* [Standardize Fields and Results](zingg-concepts/standardize-fields-and-results.md) -* [Identity Graph](zingg-concepts/identity-graph.md) +* [The graph algorithm](zingg-concepts/the-graph-algorithm.md) +* [Identity Graph](zingg-concepts/identity-graph/README.md) + * [Standardize Fields and Results](zingg-concepts/identity-graph/standardize-fields-and-results.md) * [Z Cluster and Zingg ID](zingg-concepts/z-cluster-and-zingg-id.md) * [Pass Through](zingg-concepts/pass-through.md) * [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) diff --git a/docs/platform-guides/platform-guide-for-azure-databricks.md b/docs/platform-guides/platform-guide-for-azure-databricks.md index 095187ce6..3b15af089 100644 --- a/docs/platform-guides/platform-guide-for-azure-databricks.md +++ b/docs/platform-guides/platform-guide-for-azure-databricks.md @@ -305,7 +305,7 @@ DOCS_DIR = zinggDir + "/" + modelId + "/docs/" displayHTML(open(DOCS_DIR + "model.html", 'r').read()) ``` -_**IMAGE TO BE ADDED —**** ****`generateDocs`**** ****HTML output rendered inside a Databricks notebook showing labeled pair examples. Tanwi to check with team for screenshot from a live notebook run. Even a small portion of the rendered HTML is sufficient — it tells the reader what to expect before they run it. Place: below the**** ****`displayHTML`****\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*line.**_ +_**IMAGE TO BE ADDED —**** ****`generateDocs`**** ****HTML output rendered inside a Databricks notebook showing labeled pair examples. Tanwi to check with team for screenshot from a live notebook run. Even a small portion of the rendered HTML is sufficient — it tells the reader what to expect before they run it. Place: below the**** ****`displayHTML`****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*line.**_ {% endtab %} {% tab title="Enterprise" %} @@ -628,7 +628,7 @@ data_html = "\n".join(r.value for r in data_doc.collect()) displayHTML(data_html) ``` -_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\* \*\*\*\*output rendered inside a Databricks notebook showing labeled pair examples in HTML. Tanwi to check with team for screenshot from a live notebook run. Can reuse the OS version if the output looks the same.**_ +_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*output rendered inside a Databricks notebook showing labeled pair examples in HTML. Tanwi to check with team for screenshot from a live notebook run. Can reuse the OS version if the output looks the same.**_ ### Notebook 05: Train and match diff --git a/docs/platform-guides/platform-guide-for-gcp-dataproc.md b/docs/platform-guides/platform-guide-for-gcp-dataproc.md index 2b14e5584..21f4002ef 100644 --- a/docs/platform-guides/platform-guide-for-gcp-dataproc.md +++ b/docs/platform-guides/platform-guide-for-gcp-dataproc.md @@ -411,7 +411,7 @@ with open(DOCS_DIR + "data.html", 'r') as f: display(HTML(f.read())) ``` -_**IMAGE TO BE ADDED—**** ****`generateDocs`****\*\*\*\*\*\*\*\* \*\*\*\*HTML output rendered inside JupyterLab on Dataproc showing labeled pair examples. Source: not in the GCP docx. Tanwi to check with team for screenshot from a live notebook run.**_ +_**IMAGE TO BE ADDED—**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*HTML output rendered inside JupyterLab on Dataproc showing labeled pair examples. Source: not in the GCP docx. Tanwi to check with team for screenshot from a live notebook run.**_ {% hint style="success" icon="right-long" %} `generateDocs` is optional. Skip it if you have 30–40 matches and 30–40 non-matches and are confident in your labeling quality. diff --git a/docs/platform-guides/platform-guide-for-microsoft-fabric.md b/docs/platform-guides/platform-guide-for-microsoft-fabric.md index 714b5b589..38f4272a5 100644 --- a/docs/platform-guides/platform-guide-for-microsoft-fabric.md +++ b/docs/platform-guides/platform-guide-for-microsoft-fabric.md @@ -414,7 +414,7 @@ displayHTML(open(DOCS_DIR + "model.html", 'r').read()) displayHTML(open(DOCS_DIR + "data.html", 'r').read()) ``` -_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\* \*\*\*\*HTML output rendered inside a Fabric notebook showing labeled pair examples in a table. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ +_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*HTML output rendered inside a Fabric notebook showing labeled pair examples in a table. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ ### Notebook 04: Train and match diff --git a/docs/tuning/configure-field-standardization.md b/docs/tuning/configure-field-standardization.md index dd0dffbe0..9bfdebcb3 100644 --- a/docs/tuning/configure-field-standardization.md +++ b/docs/tuning/configure-field-standardization.md @@ -129,7 +129,7 @@ If the output still shows raw variants: {% hint style="success" icon="right-long" %} **Read more**: -* [Standardize Fields and Results](../zingg-concepts/standardize-fields-and-results.md) - concept and where it is useful +* [Standardize Fields and Results](../zingg-concepts/identity-graph/standardize-fields-and-results.md) - concept and where it is useful * [Configure Zingg](../running-zingg/configure-zingg.md) - full field definition configuration including all `EFieldDefinition` methods * [Match Types](../zingg-concepts/how-zingg-learns/match-types/) - for the MAPPING match type, which uses a similar mapping file approach for matching {% endhint %} diff --git a/docs/zingg-concepts/concept-glossary.md b/docs/zingg-concepts/concept-glossary.md index dfae15300..aee7298de 100644 --- a/docs/zingg-concepts/concept-glossary.md +++ b/docs/zingg-concepts/concept-glossary.md @@ -175,7 +175,7 @@ Zingg's underlying data structure representing resolved entities and the relatio * In Enterprise, it is persistent and updated incrementally: new and changed records are incorporated without a full re-run, and Zingg IDs serve as stable node identifiers within it. {% hint style="success" icon="right-long" %} -**Read more:** [Identity graph](identity-graph.md) | [Run incremental matching](../running-zingg/run-incremental-matching.md) +**Read more:** [Identity graph](identity-graph/) | [Run incremental matching](../running-zingg/run-incremental-matching.md) {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md index 85167e6dd..c2fb14c27 100644 --- a/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md @@ -14,38 +14,13 @@ you expect. ### -### The similarity model - solving the accuracy problem -The similarity model evaluates each candidate pair that the blocking model passes through and produces a similarity score: a number between 0 and 1 reflecting how likely it is that the two records represent the same real-world entity. -It is a classifier, not a rules engine. For each pair, Zingg computes multiple features per field; character-level differences, string lengths, common transpositions, and prefix and suffix overlaps, and combines them into a single prediction. The threshold between match and no-match is automatically optimized. You do not set it manually. -The similarity model learns from your labeled pairs. Match labels show it what a true match looks like in your specific data. Non-match labels show it, what different entities look like\ -even when their field values are similar. -This is why label quality matters more than label quantity. A well-chosen set of 30 to 50 match pairs, covering the variation patterns in your schema produces a more accurate model than\ -a large set of casually labeled pairs. - -{% hint style="success" icon="right-long" %} -For diagnosing similarity model behaviour and concept details → [Similarity Model](similarity-model.md) -{% endhint %} - -### The graph algorithm - from pairs to clusters - -After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure. - -If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared. - -This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations. - -In Community, clusters are assigned a `Z Cluster` that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent `Zingg ID` (GUID) that remains stable across runs, incremental updates, and model changes. +### {% hint style="success" icon="right-long" %} -**Read more**: - -* [Identity Graph](../../identity-graph.md) -* [Z Cluster ID vs Zingg ID](../../z-cluster-and-zingg-id.md) -* [Zingg ID](/broken/pages/9QpDFW20AMt0UJ4cEW6b) * Run [Incremental Matching](../../../running-zingg/run-incremental-matching.md) - how the graph grows over time {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md index 1cefd1a19..e0d4b38ac 100644 --- a/docs/zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md @@ -1,11 +1,31 @@ --- -description: >- - How to inspect the similarity model, diagnose false positives and false - negatives, and improve accuracy through targeted retraining. +description: solving the accuracy problem --- # Similarity Model + + +The similarity model evaluates each candidate pair that the blocking model passes through and produces a similarity score: a number between 0 and 1 reflecting how likely it is that the two records represent the same real-world entity. + +It is a classifier, not a rules engine. For each pair, Zingg computes multiple features per field; character-level differences, string lengths, common transpositions, and prefix and suffix overlaps, and combines them into a single prediction. The threshold between match and no-match is automatically optimized. You do not set it manually. + +The similarity model learns from your labeled pairs. Match labels show it what a true match looks like in your specific data. Non-match labels show it, what different entities look like\ +even when their field values are similar. + +This is why label quality matters more than label quantity. A well-chosen set of 30 to 50 match pairs, covering the variation patterns in your schema produces a more accurate model than\ +a large set of casually labeled pairs. + +{% hint style="success" icon="right-long" %} +For diagnosing similarity model behaviour and concept details → [Similarity Model](similarity-model.md) +{% endhint %} + + + + + +How to inspect the similarity model, diagnose false positives and false negatives, and improve accuracy through targeted retraining.\ +\ The similarity model scores every candidate pair that the blocking model passes through. If your results contain records incorrectly merged into the same cluster (false positives) or matching records that were missed (false negatives), the similarity model is where to investigate after first confirming that blocking is not the cause. {% hint style="success" icon="right-long" %} @@ -70,4 +90,3 @@ Ordering clusters by `avg_min` ascending puts the weakest clusters at the top, t * [Label Training Pairs](../../../running-zingg/label-training-pairs.md) - adding training data for missed patterns * [Verify blocking](../../../running-zingg/verify-blocking.md) {% endhint %} - diff --git a/docs/zingg-concepts/identity-graph.md b/docs/zingg-concepts/identity-graph/README.md similarity index 92% rename from docs/zingg-concepts/identity-graph.md rename to docs/zingg-concepts/identity-graph/README.md index d6510f17c..89f6c4adc 100644 --- a/docs/zingg-concepts/identity-graph.md +++ b/docs/zingg-concepts/identity-graph/README.md @@ -43,8 +43,8 @@ Enterprise gives you the same graph with stable `Zingg IDs` , so the graph can g **Read more**: -* [Z Cluster vs Zingg ID](z-cluster-and-zingg-id.md) -* [Zingg Models](how-zingg-learns/zingg-models/) -* [Run incremental matching](../running-zingg/run-incremental-matching.md) -* [Community Vs Enterprise](community-vs-enterprise/) +* [Z Cluster vs Zingg ID](../z-cluster-and-zingg-id.md) +* [Zingg Models](../how-zingg-learns/zingg-models/) +* [Run incremental matching](../../running-zingg/run-incremental-matching.md) +* [Community Vs Enterprise](../community-vs-enterprise/) {% endhint %} diff --git a/docs/zingg-concepts/standardize-fields-and-results.md b/docs/zingg-concepts/identity-graph/standardize-fields-and-results.md similarity index 92% rename from docs/zingg-concepts/standardize-fields-and-results.md rename to docs/zingg-concepts/identity-graph/standardize-fields-and-results.md index b8b14d15d..3898e2d5d 100644 --- a/docs/zingg-concepts/standardize-fields-and-results.md +++ b/docs/zingg-concepts/identity-graph/standardize-fields-and-results.md @@ -60,7 +60,7 @@ Enforce canonical values on classification fields like industry codes, status la {% hint style="success" icon="right-long" %} **Read more**: -* To create a mapping file and configure the postprocessor on a field → [Configure Field Standardization](../tuning/configure-field-standardization.md) -* For the full field definition configuration including all `EFieldDefinition` methods - [Configure Zingg](../running-zingg/configure-zingg.md) -* For dictionary-based matching using the MAPPING match type (nicknames, aliases, company names) - [Match Types](how-zingg-learns/match-types/) +* To create a mapping file and configure the postprocessor on a field → [Configure Field Standardization](../../tuning/configure-field-standardization.md) +* For the full field definition configuration including all `EFieldDefinition` methods - [Configure Zingg](../../running-zingg/configure-zingg.md) +* For dictionary-based matching using the MAPPING match type (nicknames, aliases, company names) - [Match Types](../how-zingg-learns/match-types/) {% endhint %} diff --git a/docs/zingg-concepts/the-graph-algorithm.md b/docs/zingg-concepts/the-graph-algorithm.md new file mode 100644 index 000000000..aabf4c888 --- /dev/null +++ b/docs/zingg-concepts/the-graph-algorithm.md @@ -0,0 +1,21 @@ +--- +description: from pairs to clusters +--- + +# The graph algorithm + +After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure. + +If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared. + +This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations. + +In Community, clusters are assigned a `Z Cluster` that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent `Zingg ID` (GUID) that remains stable across runs, incremental updates, and model changes. + +{% hint style="success" icon="right-long" %} +**Read more**: + +* [Identity Graph](identity-graph/) +* [Z Cluster ID vs Zingg ID](z-cluster-and-zingg-id.md) +* [Zingg ID](/broken/pages/9QpDFW20AMt0UJ4cEW6b) +{% endhint %} diff --git a/docs/zingg-concepts/z-cluster-and-zingg-id.md b/docs/zingg-concepts/z-cluster-and-zingg-id.md index e153bc3be..fa4765232 100644 --- a/docs/zingg-concepts/z-cluster-and-zingg-id.md +++ b/docs/zingg-concepts/z-cluster-and-zingg-id.md @@ -62,7 +62,7 @@ Use this column to: **Read more:** * [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) - for the full reference of every Zingg output column -* [Identity Graph](identity-graph.md) - how Zingg's resolved entities form a graph structure +* [Identity Graph](identity-graph/) - how Zingg's resolved entities form a graph structure * [Run Incremental Matching](../running-zingg/run-incremental-matching.md) - how Zingg IDs persist when new records arrive * [Reassign Zingg ID](../running-zingg/reassign-zingg-id.md) - preserve Zingg IDs across model retraining or platform migration * [Concept Glossary](concept-glossary.md) - every Zingg term in one place diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/README.md b/docs/zingg-concepts/zingg-entity-resolution-platform/README.md index 50893e32f..5d3fe289c 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/README.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/README.md @@ -4,11 +4,9 @@ Zingg is an ML-powered entity resolution engine built to run where your data alr Three capabilities work together to handle the full problem: -
Warehouse-native executionProbabilistic + deterministic matchingPersistent identity graph
Zingg runs inside Databricks, Microsoft
Fabric, Snowflake, GCP Dataproc, AWS Glue, and AWS EMR. Your data never
leaves your environment. No ETL pipelines. No external APIs. There is no separate infrastructure to operate. The same model that runs on 100,000 records scales to hundreds of millions using your existing Spark or Snowflake compute, without any architectural changes.

Probabilistic matching is Zingg's default and available in the Community Edition. The ML model learns from your labeled pairs; 30 to 50 examples are enough to build a model calibrated
to your specific data and scores every candidate pair on multiple field-level features. It handles
typos, abbreviations, missing values, and format variations automatically.

Deterministic matching (Enterprise) adds hard rules for trusted identifiers.
When two records share the same SSN, tax ID, or email, Zingg treats them
as the same entity without consulting the ML model.

Both approaches run in a single flow, finding all possible matches.

Deterministic vs Probabilistic
Matching

Every resolved entity receives a Zingg ID, a globally unique, persistent GUID assigned in Enterprise that remains stable
across runs, incremental updates, and model changes.

Community produces a Z Cluster
which is unique. Matching records share the same Z_Cluster that is reassigned each run.


Enterprise produces a Zingg ID you can store in downstream systems
with confidence.

The identity graph grows incrementally. New records are matched to existing clusters without rerunning on your full dataset.

Identity Graph
Z Cluster and Zingg ID

- -This is the combination that makes enterprise-scale entity resolution computationally feasible and practically maintainable. - +
Warehouse-native executionProbabilistic + deterministic matchingPersistent identity graph
Zingg runs inside Databricks, Microsoft
Fabric, Snowflake, GCP Dataproc, AWS Glue, and AWS EMR. Your data never
leaves your environment. No ETL pipelines. No external APIs. There is no separate infrastructure to operate. The same model that runs on 100,000 records scales to hundreds of millions using your existing Spark or Snowflake compute, without any architectural changes.

Probabilistic matching is Zingg's default and available in the Community Edition. The ML model learns from your labeled pairs; 30 to 50 examples are enough to build a model calibrated
to your specific data and scores every candidate pair on multiple field-level features. It handles
typos, abbreviations, missing values, and format variations automatically.

Deterministic matching (Enterprise) adds hard rules for trusted identifiers.
When two records share the same SSN, tax ID, or email, Zingg treats them
as the same entity without consulting the ML model.

Both approaches run in a single flow, finding all possible matches.

Deterministic vs Probabilistic
Matching

Every resolved entity receives a Zingg ID, a globally unique, persistent GUID assigned in Enterprise that remains stable
across runs, incremental updates, and model changes.

Community produces a Z Cluster
which is unique. Matching records share the same Z_Cluster that is reassigned each run.


Enterprise produces a Zingg ID you can store in downstream systems
with confidence.

The identity graph grows incrementally. New records are matched to existing clusters without rerunning on your full dataset.

Identity Graph
Z Cluster and Zingg ID

+This is the combination that makes enterprise-scale entity resolution computationally feasible and practically maintainable. {% hint style="success" icon="right-long" %} **Read more**: @@ -17,5 +15,3 @@ This is the combination that makes enterprise-scale entity resolution computatio * [How Zingg Learns](../how-zingg-learns/) * [Zingg Models](../how-zingg-learns/zingg-models/) (blocking + similarity) {% endhint %} - -[^1]: From 3de3eb5b611ea07b93b2ab660fab19d2a608f352 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Mon, 17 Aug 2026 16:19:17 +0000 Subject: [PATCH 071/144] GITBOOK-350: No subject --- docs/SUMMARY.md | 2 +- .../platform-guide-for-azure-databricks.md | 2 +- docs/{zingg-concepts => running-zingg}/pass-through.md | 6 +++--- docs/zingg-concepts/community-vs-enterprise/README.md | 8 ++++---- docs/zingg-concepts/concept-glossary.md | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) rename docs/{zingg-concepts => running-zingg}/pass-through.md (92%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 7b7edb6e9..145d2391e 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -34,7 +34,6 @@ * [Identity Graph](zingg-concepts/identity-graph/README.md) * [Standardize Fields and Results](zingg-concepts/identity-graph/standardize-fields-and-results.md) * [Z Cluster and Zingg ID](zingg-concepts/z-cluster-and-zingg-id.md) -* [Pass Through](zingg-concepts/pass-through.md) * [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) * [Concept Glossary](zingg-concepts/concept-glossary.md) @@ -46,6 +45,7 @@ * [Quick Start (Docker)](running-zingg/quick-start-docker.md) * [Install Zingg](running-zingg/install-zingg.md) * [Configure Zingg](running-zingg/configure-zingg.md) +* [Pass Through](running-zingg/pass-through.md) * [Create Training Data](running-zingg/create-training-data.md) * [Label Training Pairs](running-zingg/label-training-pairs.md) * [Verify Blocking](running-zingg/verify-blocking.md) diff --git a/docs/platform-guides/platform-guide-for-azure-databricks.md b/docs/platform-guides/platform-guide-for-azure-databricks.md index 3b15af089..c651eff4b 100644 --- a/docs/platform-guides/platform-guide-for-azure-databricks.md +++ b/docs/platform-guides/platform-guide-for-azure-databricks.md @@ -502,7 +502,7 @@ args.setPassthroughExpr("fname = 'matilda'") **Read more**: * For deterministic matching concepts → [Configure Zingg](../running-zingg/configure-zingg.md) -* For pass-through → [Pass Through](../zingg-concepts/pass-through.md) +* For pass-through → [Pass Through](../running-zingg/pass-through.md) {% endhint %} ### Notebook 02: Remove stopwords (Optional) diff --git a/docs/zingg-concepts/pass-through.md b/docs/running-zingg/pass-through.md similarity index 92% rename from docs/zingg-concepts/pass-through.md rename to docs/running-zingg/pass-through.md index 23c7f8f40..aa2e9c932 100644 --- a/docs/zingg-concepts/pass-through.md +++ b/docs/running-zingg/pass-through.md @@ -57,7 +57,7 @@ Occasionally a specific attribute value should prevent a record from influencing ### How to configure Pass Through -`Pass Through` is not a separate phase; it is a filter expression you set on your `args` object in [Configure Zingg](../running-zingg/configure-zingg.md), before running any phase. Records matching the expression are excluded from matching, linking, and incremental runs automatically. +`Pass Through` is not a separate phase; it is a filter expression you set on your `args` object in [Configure Zingg](configure-zingg.md), before running any phase. Records matching the expression are excluded from matching, linking, and incremental runs automatically. {% tabs %} {% tab title="Enterprise" %} @@ -99,6 +99,6 @@ _**CHECK WITH SONAL - Enterprise Snowflake content for this topic to be provivde {% hint style="success" icon="right-long" %} **Read more**: -* Pass Through concept in the glossary - [Concepts glossary](concept-glossary.md) -* Configure Zingg where Pass Through is set up - [Configure Zingg](../running-zingg/configure-zingg.md) +* Pass Through concept in the glossary - [Concepts glossary](../zingg-concepts/concept-glossary.md) +* Configure Zingg where Pass Through is set up - [Configure Zingg](configure-zingg.md) {% endhint %} diff --git a/docs/zingg-concepts/community-vs-enterprise/README.md b/docs/zingg-concepts/community-vs-enterprise/README.md index eaaabb497..5469220ae 100644 --- a/docs/zingg-concepts/community-vs-enterprise/README.md +++ b/docs/zingg-concepts/community-vs-enterprise/README.md @@ -6,9 +6,9 @@ description: >- # Community vs Enterprise -Every Zingg edition is built on the same foundation: probabilistic matching, active learning, and warehouse-native execution. What changes is the identity infrastructure, operational control, and advanced matching capability you obtain on top of it. +Every Zingg edition is built on the same foundation: probabilistic matching, active learning, and warehouse-native execution. What changes is the identity infrastructure, operational control, and advanced matching capability you obtain on top of it. -
CommunityEnterprise LiteEnterprise
Who it is forTeams that are evaluating Zingg, running proofs of concept, or do not need a persistent identity graph with continously updated data.Teams that want to run entity resolution into production and need persistent entity IDs, deterministic matching and incremental updates.Teams who need full lifecycle control: seamless model upgrades, governance, explainability, and dictionary-based matching for complex data.
Identity graphZ Cluster is a unique cluster identifier assigned per run.Persistent Zingg ID (GUID). Stable across runs and safe to reference in CRMs, warehouses, and pipelines.Persistent Zingg ID plus seamless ID reassignment when you upgrade your model. Downstream systems stay intact across changes.
Matching capabilityProbabilistic matching trained on your labeled pairs. Handles typos, abbreviations, and variations across any field type.Probabilistic + deterministic matching in a single flow. Hard rules for trusted identifiers like SSN, tax ID, email. Pass Through for records that should appear in output but not influence matching.

Everything in Lite, plus dictionary-based matching (nicknames, aliases, company name variants).

Match statistics and explainability for audit and governance.

Model operationsTrain once and match as data arrives.
  • Incremental runs update the identity graph as new records arrive without retraining.
  • Verify blocking before committing to matching.

Everything in Lite, plus

  • Upgrade models without disrupting Zingg IDs.
  • Compare two trained models side by side before deploying.
  • Re-assign IDs from an old model to a new one.
PlatformAny Spark environment - Databricks, Fabric, EMR, GCP Dataproc, and local Spark.All Community platforms plus
Native Snowflake run (no Spark cluster required)

All platforms in Lite.
+
CommunityEnterprise LiteEnterprise
Who it is forTeams that are evaluating Zingg, running proofs of concept, or do not need a persistent identity graph with continously updated data.Teams that want to run entity resolution into production and need persistent entity IDs, deterministic matching and incremental updates.Teams who need full lifecycle control: seamless model upgrades, governance, explainability, and dictionary-based matching for complex data.
Identity graphZ Cluster is a unique cluster identifier assigned per run.Persistent Zingg ID (GUID). Stable across runs and safe to reference in CRMs, warehouses, and pipelines.Persistent Zingg ID plus seamless ID reassignment when you upgrade your model. Downstream systems stay intact across changes.
Matching capabilityProbabilistic matching trained on your labeled pairs. Handles typos, abbreviations, and variations across any field type.Probabilistic + deterministic matching in a single flow. Hard rules for trusted identifiers like SSN, tax ID, email. Pass Through for records that should appear in output but not influence matching.

Everything in Lite, plus dictionary-based matching (nicknames, aliases, company name variants).

Match statistics and explainability for audit and governance.

Model operationsTrain once and match as data arrives.
  • Incremental runs update the identity graph as new records arrive without retraining.
  • Verify blocking before committing to matching.

Everything in Lite, plus

  • Upgrade models without disrupting Zingg IDs.
  • Compare two trained models side by side before deploying.
  • Re-assign IDs from an old model to a new one.
PlatformAny Spark environment - Databricks, Fabric, EMR, GCP Dataproc, and local Spark.All Community platforms plus
Native Snowflake run (no Spark cluster required)

All platforms in Lite.
### When to choose each edition @@ -18,7 +18,7 @@ You can match any entity type, like customers, patients, suppliers, products, an _The limitation is operational:_ Community's `Z Cluster` is non-persistent. If you re-run the match job, cluster IDs may change. You cannot safely store them in downstream systems. That is the boundary where Community ends and Enterprise begins.\ \ -_The limitation is also in the resolution:_ Compared to any other, Zingg Community is the most powerful entity resolution product on the planet. Zingg Enterprise is even more powerful. +_The limitation is also in the resolution:_ Compared to any other, Zingg Community is the most powerful entity resolution product on the planet. Zingg Enterprise is even more powerful.
@@ -32,7 +32,7 @@ Enterprise Lite also gives you: * [**Incremental matching**](../../running-zingg/run-incremental-matching.md) **-** update the identity graph as new records arrive, without re-running on your entire dataset * [**Deterministic matching**](../zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) **-** combine hard rules for known trusted identifiers (SSN, tax ID, email) with probabilistic ML in a single flow -* [**Pass Through**](../pass-through.md) **-** records that are incomplete or unsuitable for matching still appear in your output and receive a Zingg ID for governance and compliance +* [**Pass Through**](../../running-zingg/pass-through.md) **-** records that are incomplete or unsuitable for matching still appear in your output and receive a Zingg ID for governance and compliance * [**Production-grade notebooks**](../../platform-guides/platform-guide-for-azure-databricks.md) **-** a 7-notebook sequence, one per workflow phase, built for repeatable production runs Clients who move to Enterprise Lite are typically building Customer 360 views, patient master indexes, or supplier deduplication pipelines where the entity identifier needs to be stable and trusted downstream. diff --git a/docs/zingg-concepts/concept-glossary.md b/docs/zingg-concepts/concept-glossary.md index aee7298de..9f1bf48a5 100644 --- a/docs/zingg-concepts/concept-glossary.md +++ b/docs/zingg-concepts/concept-glossary.md @@ -319,7 +319,7 @@ An Enterprise feature for records that are partially populated, outdated, or not _Common uses_: Records flagged as deceased, bot-generated accounts identifiable by a synthetic email pattern, or corrupted records that must be traceable but should not influence cluster formation. {% hint style="success" icon="right-long" %} -**Read more:** [Pass Through](pass-through.md) +**Read more:** [Pass Through](../running-zingg/pass-through.md) {% endhint %}
From dfe961bea014aba4c6347d527184ba504f614a5c Mon Sep 17 00:00:00 2001 From: Chandan Date: Tue, 18 Aug 2026 09:06:37 +0530 Subject: [PATCH 072/144] Remove broken JsonStringArgumentsWriter (#1306) (#1378) Its write() reassigned the local sink parameter, so the serialized JSON was discarded and never returned to the caller. The class was also dead code: writeArguments always uses WriterType.FILE and nothing invoked the JSON path. Removed the class, the WriterFactory JSON case, and the WriterType.JSON constant. Co-authored-by: chandan Co-authored-by: Claude Opus 4.8 --- .../writer/JsonStringArgumentsWriter.java | 16 ---------------- .../client/arguments/writer/WriterFactory.java | 4 +--- .../client/arguments/writer/WriterType.java | 3 +-- 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 common/client/src/main/java/zingg/common/client/arguments/writer/JsonStringArgumentsWriter.java diff --git a/common/client/src/main/java/zingg/common/client/arguments/writer/JsonStringArgumentsWriter.java b/common/client/src/main/java/zingg/common/client/arguments/writer/JsonStringArgumentsWriter.java deleted file mode 100644 index a135e0631..000000000 --- a/common/client/src/main/java/zingg/common/client/arguments/writer/JsonStringArgumentsWriter.java +++ /dev/null @@ -1,16 +0,0 @@ -package zingg.common.client.arguments.writer; - -import zingg.common.client.arguments.model.IZArgs; -import zingg.common.client.ZinggClientException; - -public class JsonStringArgumentsWriter extends ArgumentsWriter { - - @Override - public void write(String sink, IZArgs args) throws ZinggClientException { - try { - sink = objectMapper.writeValueAsString(args); - } catch (Exception exception) { - throw new ZinggClientException("Error writing config to string", exception); - } - } -} diff --git a/common/client/src/main/java/zingg/common/client/arguments/writer/WriterFactory.java b/common/client/src/main/java/zingg/common/client/arguments/writer/WriterFactory.java index fb0068fa8..b0e920897 100644 --- a/common/client/src/main/java/zingg/common/client/arguments/writer/WriterFactory.java +++ b/common/client/src/main/java/zingg/common/client/arguments/writer/WriterFactory.java @@ -10,10 +10,8 @@ public ArgumentsWriter getArgumentsWriter(WriterType writerType) throws NoSuc switch (writerType) { case FILE: return new FileArgumentsWriter(); - case JSON: - return new JsonStringArgumentsWriter(); default: throw new NoSuchObjectException("No such writer exists: " + writerType.name()); } } -} \ No newline at end of file +} diff --git a/common/client/src/main/java/zingg/common/client/arguments/writer/WriterType.java b/common/client/src/main/java/zingg/common/client/arguments/writer/WriterType.java index 545a1b828..a37d3399d 100644 --- a/common/client/src/main/java/zingg/common/client/arguments/writer/WriterType.java +++ b/common/client/src/main/java/zingg/common/client/arguments/writer/WriterType.java @@ -1,6 +1,5 @@ package zingg.common.client.arguments.writer; public enum WriterType { - FILE, - JSON + FILE } From 2a9647a6ccdbf0e9b144c44d51c9a201a4cf98a4 Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Tue, 18 Aug 2026 09:08:49 +0530 Subject: [PATCH 073/144] Format ClientOptions initialization in code examples Reformat Python code examples in documentation to use single-line ClientOptions initialization instead of multi-line formatting. This improves readability and consistency across all platform guides and API documentation. --- .../explain-a-specific-cluster.md | 2 +- .../platform-guide-for-aws-glue.md | 9 ++--- .../platform-guide-for-azure-databricks.md | 30 ++++++++-------- .../platform-guide-for-gcp-dataproc.md | 12 +++---- .../platform-guide-for-microsoft-fabric.md | 12 +++---- .../running-zingg/build-and-save-the-model.md | 10 ++---- docs/running-zingg/compare-model-results.md | 5 +-- docs/running-zingg/create-training-data.md | 10 ++---- .../generate-model-documentation.md | 10 ++---- docs/running-zingg/label-training-pairs.md | 10 ++---- docs/running-zingg/link-across-datasets.md | 10 ++---- docs/running-zingg/quick-start-docker.md | 16 ++++----- docs/running-zingg/reassign-zingg-id.md | 10 ++---- .../running-zingg/run-incremental-matching.md | 5 +-- docs/running-zingg/run-the-match-phase.md | 5 +-- docs/running-zingg/update-label.md | 5 +-- docs/running-zingg/verify-blocking.md | 10 ++---- .../remove-stopwords-optional.md | 4 +-- docs/zingg-python-api/community-python-api.md | 21 +++++------- .../enterprise-zinggec-python-api.md | 34 ++++++++----------- .../enterprise-zingges-python-api.md | 6 ++-- 21 files changed, 84 insertions(+), 152 deletions(-) diff --git a/docs/interpreting-results/explain-a-specific-cluster.md b/docs/interpreting-results/explain-a-specific-cluster.md index 8a17fd9be..ca669c3e8 100644 --- a/docs/interpreting-results/explain-a-specific-cluster.md +++ b/docs/interpreting-results/explain-a-specific-cluster.md @@ -53,7 +53,7 @@ args.setZinggId("ea67d79a-56a7-4431-ab55-d08bb3c10e2e") ### **Step 4: Run the explain phase** ```python -options = ClientOptions([ ClientOptions.PHASE, "explainOutput" ]) +options = ClientOptions([ClientOptions.PHASE, "explainOutput"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` diff --git a/docs/platform-guides/platform-guide-for-aws-glue.md b/docs/platform-guides/platform-guide-for-aws-glue.md index 4e0a9026b..c9f1ed441 100644 --- a/docs/platform-guides/platform-guide-for-aws-glue.md +++ b/docs/platform-guides/platform-guide-for-aws-glue.md @@ -383,8 +383,7 @@ AWS Glue Interactive Sessions restrict standard Jupyter widgets. Zingg's standar Zingg scans your dataset using the field rules defined in Step 12 and selects the most informative pairs for labeling. Candidate pairs are saved to `UNMARKED_DIR` in your S3 bucket. ```python -options = ClientOptions([ - ClientOptions.PHASE, "findTrainingData"]) +options = ClientOptions([ClientOptions.PHASE, "findTrainingData"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() ``` @@ -568,8 +567,7 @@ else: Run `generateDocs` after labeling to produce a model report showing field weights, training data quality, and precision and recall estimates. The HTML report is written to your S3 model directory and can be downloaded from the S3 console. ```python -options = ClientOptions([ - ClientOptions.PHASE, "generateDocs"]) +options = ClientOptions([ClientOptions.PHASE, "generateDocs"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() @@ -598,8 +596,7 @@ Unlike other platforms, Glue cannot render HTML inline in the notebook. Download `trainMatch` combines `train` and `match` into a single phase. Zingg builds a model from your labeled pairs and immediately applies it to the full dataset. This is the most compute-intensive step; it distributes the workload across all Glue workers. ```python -options = ClientOptions([ - ClientOptions.PHASE, "trainMatch"]) +options = ClientOptions([ClientOptions.PHASE, "trainMatch"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() diff --git a/docs/platform-guides/platform-guide-for-azure-databricks.md b/docs/platform-guides/platform-guide-for-azure-databricks.md index c651eff4b..b1843bdf8 100644 --- a/docs/platform-guides/platform-guide-for-azure-databricks.md +++ b/docs/platform-guides/platform-guide-for-azure-databricks.md @@ -176,8 +176,7 @@ ssn = FieldDefinition("ssn", "STRING", MatchType.EXACT) args.setFieldDefinition([ recId, fName, lName, streetId, street, locality, area, areaCode, state, dob, - ssn -]) + ssn]) ``` {% hint style="success" icon="right-long" %} @@ -191,7 +190,7 @@ args.setFieldDefinition([ Zingg scans your data and selects the most informative pairs for labeling—edge cases where the model has the most to learn. Run this before labeling. ```python -options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +options = ClientOptions([ClientOptions.PHASE, "findTrainingData"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() ``` @@ -199,7 +198,7 @@ zingg.initAndExecute() #### Step 11: Load pairs for labeling ```python -options = ClientOptions([ ClientOptions.PHASE, "label" ]) +options = ClientOptions([ClientOptions.PHASE, "label"]) zingg = ZinggWithSpark(args, options) zingg.init() @@ -250,7 +249,7 @@ print(f"You have accumulated {n_uncer} pairs labeled as uncertain.") Before training, review the pairs you have labeled to verify quality. This uses the `updateLabel` phase to surface your marked records for inspection. ```python -options = ClientOptions([ ClientOptions.PHASE, "updateLabel" ]) +options = ClientOptions([ClientOptions.PHASE, "updateLabel"]) zingg = ZinggWithSpark(args, options) zingg.init() @@ -263,7 +262,7 @@ display(markedRecords) Zingg builds the blocking and similarity models from your labeled pairs and persists them to `zinggDir/modelId`. Once trained, this model can be reused on new data without retraining. ```python -options = ClientOptions([ ClientOptions.PHASE, "train" ]) +options = ClientOptions([ClientOptions.PHASE, "train"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() ``` @@ -273,7 +272,7 @@ zingg.initAndExecute() Applies the trained model to your full dataset and writes resolved clusters to the output location configured in Step 8. ```python -options = ClientOptions([ ClientOptions.PHASE, "match" ]) +options = ClientOptions([ClientOptions.PHASE, "match"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() ``` @@ -297,7 +296,7 @@ Records sharing the same `Z_CLUSTER` value have been resolved to the same real-w Run `generateDocs` after labeling to produce readable HTML documentation of your training data—both matched and non-matched pairs. Run it before training to inspect data quality, or share the output with subject matter experts to validate labels before committing to train. ```python -options = ClientOptions([ ClientOptions.PHASE, "generateDocs" ]) +options = ClientOptions([ClientOptions.PHASE, "generateDocs"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() @@ -516,7 +515,7 @@ Zingg analyses the specified column and returns a list of high-frequency words i ```python stopwordcolumn = "street" args.setColumn(stopwordcolumn) -options = ClientOptions([ ClientOptions.PHASE, "recommend" ]) +options = ClientOptions([ClientOptions.PHASE, "recommend"]) zingg = EZinggWithSpark(args, options) zingg.initAndExecute() ``` @@ -563,7 +562,7 @@ For 100k records use 0.1–0.5. For 1M records use 0.01–0.05. If `findTraining #### Step 17: Find candidate pairs ```python -options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +options = ClientOptions([ClientOptions.PHASE, "findTrainingData"]) zingg = EZinggWithSpark(args, options) zingg.initAndExecute() ``` @@ -571,7 +570,7 @@ zingg.initAndExecute() #### Step 18: Load pairs for labeling ```python -options = ClientOptions([ ClientOptions.PHASE, "label" ]) +options = ClientOptions([ClientOptions.PHASE, "label"]) zingg = EZinggWithSpark(args, options) zingg.init() @@ -612,8 +611,7 @@ print(f"You have accumulated {n_uncer} pairs labeled as uncertain.") Run `generateDocs` after labeling and before training to inspect training data quality. Produces readable HTML documentation of your labeled pairs—both matches and non-matches. ```python -options = ClientOptions([ - ClientOptions.PHASE, "generateDocs"]) +options = ClientOptions([ClientOptions.PHASE, "generateDocs"]) zingg = EZinggWithSpark(args, options) zingg.initAndExecute() @@ -635,7 +633,7 @@ _**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\* #### **Step 21: Train the model** ```python -options = ClientOptions([ ClientOptions.PHASE, "train" ]) +options = ClientOptions([ClientOptions.PHASE, "train"]) zingg = EZinggWithSpark(args, options) zingg.initAndExecute() ``` @@ -643,7 +641,7 @@ zingg.initAndExecute() #### Step 22: Run the match ```python -options = ClientOptions([ ClientOptions.PHASE, "match" ]) +options = ClientOptions([ClientOptions.PHASE, "match"]) zingg = EZinggWithSpark(args, options) zingg.initAndExecute() ``` @@ -694,7 +692,7 @@ incrArgs.setOutputTmp(outputTmpPipe) #### Step 25: Run incremental ```python -options = ClientOptions([ ClientOptions.PHASE, "runIncremental" ]) +options = ClientOptions([ClientOptions.PHASE, "runIncremental"]) zingg = EZinggWithSpark(incrArgs, options) zingg.initAndExecute() ``` diff --git a/docs/platform-guides/platform-guide-for-gcp-dataproc.md b/docs/platform-guides/platform-guide-for-gcp-dataproc.md index 21f4002ef..ca33a07a4 100644 --- a/docs/platform-guides/platform-guide-for-gcp-dataproc.md +++ b/docs/platform-guides/platform-guide-for-gcp-dataproc.md @@ -286,7 +286,7 @@ For 100k records use `labelDataSampleSize` between 0.1 and 0.5. For 1M+ records Zingg scans your dataset using the field rules defined in Step 9 and identifies pairs of records the model is uncertain about — edge cases where human input is most valuable. Candidate pairs are saved to `UNMARKED_DIR` in your GCS bucket. ```python -options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +options = ClientOptions([ClientOptions.PHASE, "findTrainingData"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() ``` @@ -294,7 +294,7 @@ zingg.initAndExecute() ### Step 12: Load pairs for labeling ```python -options = ClientOptions([ ClientOptions.PHASE, "label" ]) +options = ClientOptions([ClientOptions.PHASE, "label"]) zingg = ZinggWithSpark(args, options) zingg.init() @@ -395,7 +395,7 @@ else: Run `generateDocs` after labeling to produce readable HTML reports of your training data, both matched and non-matched pairs. Use this to verify label consistency and share a visual audit with subject matter experts before training. ```python -options = ClientOptions([ ClientOptions.PHASE, "generateDocs" ]) +options = ClientOptions([ClientOptions.PHASE, "generateDocs"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() @@ -422,7 +422,7 @@ _**IMAGE TO BE ADDED—**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\* `trainMatch` combines the `train` and `match` phases into a single call. Zingg builds a model from your labeled pairs and immediately applies it to the full dataset. This is the most compute-intensive step — it distributes the workload across all Dataproc nodes. ```python -options = ClientOptions([ ClientOptions.PHASE, "trainMatch" ]) +options = ClientOptions([ClientOptions.PHASE, "trainMatch"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() ``` @@ -432,7 +432,7 @@ You can also run `train` and `match` as two separate phases if you want to inspe Train separately: ```python -options = ClientOptions([ ClientOptions.PHASE, "train" ]) +options = ClientOptions([ClientOptions.PHASE, "train"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() ``` @@ -440,7 +440,7 @@ zingg.initAndExecute() Then match separately: ```python -options = ClientOptions([ ClientOptions.PHASE, "match" ]) +options = ClientOptions([ClientOptions.PHASE, "match"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() ``` diff --git a/docs/platform-guides/platform-guide-for-microsoft-fabric.md b/docs/platform-guides/platform-guide-for-microsoft-fabric.md index 38f4272a5..f29e89ca0 100644 --- a/docs/platform-guides/platform-guide-for-microsoft-fabric.md +++ b/docs/platform-guides/platform-guide-for-microsoft-fabric.md @@ -282,7 +282,7 @@ For 100k records use `labelDataSampleSize` between 0.1 and 0.5. For 1M+ records Zingg scans your dataset using the field rules defined in Step 13 and selects the most informative pairs for labeling — edge cases where the model has the most to learn. Candidate pairs are saved to `UNMARKED_DIR` in your OneLake Lakehouse. ```python -options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +options = ClientOptions([ClientOptions.PHASE, "findTrainingData"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() ``` @@ -290,7 +290,7 @@ zingg.initAndExecute() #### Step 16: Load pairs for labeling ```python -options = ClientOptions([ ClientOptions.PHASE, "label" ]) +options = ClientOptions([ClientOptions.PHASE, "label"]) zingg = ZinggWithSpark(args, options) zingg.init() @@ -404,7 +404,7 @@ This notebook runs `generateDocs`. It calls `%run 01-setting_up_zingg` at the to Run `generateDocs` to produce readable HTML reports of your labeled training data — both matched and non-matched pairs. Use this to verify label consistency and share with subject matter experts before committing to training. ```python -options = ClientOptions([ ClientOptions.PHASE, "generateDocs" ]) +options = ClientOptions([ClientOptions.PHASE, "generateDocs"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() @@ -425,7 +425,7 @@ This notebook runs `trainMatch` and displays the output. It calls `%run 01-setti `trainMatch` combines `train` and `match` into a single phase. Zingg builds a model from your labeled pairs and immediately applies it to the full dataset. This is the most compute-intensive step; Spark distributes the workload across all Fabric Spark nodes. ```python -options = ClientOptions([ ClientOptions.PHASE, "trainMatch" ]) +options = ClientOptions([ClientOptions.PHASE, "trainMatch"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() ``` @@ -433,11 +433,11 @@ zingg.initAndExecute() You can also run `train` and `match` as separate phases if you want to inspect the trained model before running the full dataset: ```python -options = ClientOptions([ ClientOptions.PHASE, "train" ]) +options = ClientOptions([ClientOptions.PHASE, "train"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() -options = ClientOptions([ ClientOptions.PHASE, "match" ]) +options = ClientOptions([ClientOptions.PHASE, "match"]) zingg = ZinggWithSpark(args, options) zingg.initAndExecute() ``` diff --git a/docs/running-zingg/build-and-save-the-model.md b/docs/running-zingg/build-and-save-the-model.md index b53ae225b..48d741067 100644 --- a/docs/running-zingg/build-and-save-the-model.md +++ b/docs/running-zingg/build-and-save-the-model.md @@ -21,10 +21,7 @@ Use the same `modelId` when running `match`, `link`, or `runIncremental` to appl ### Python ```python -options = ClientOptions([ - ClientOptions.PHASE, - "train" -]) +options = ClientOptions([ClientOptions.PHASE, "train"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -40,10 +37,7 @@ zingg.initAndExecute() ### Python ```python -options = ClientOptions([ - ClientOptions.PHASE, - "train" -]) +options = ClientOptions([ClientOptions.PHASE, "train"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` diff --git a/docs/running-zingg/compare-model-results.md b/docs/running-zingg/compare-model-results.md index 7a4e3d0ac..7cb6acf83 100644 --- a/docs/running-zingg/compare-model-results.md +++ b/docs/running-zingg/compare-model-results.md @@ -96,10 +96,7 @@ diffArgs.setResults(diffOutputPipe) #### Step 4: Execute diff ```python -diffOptions = EClientOptions([ - EClientOptions.PHASE, - "diff" -]) +diffOptions = EClientOptions([EClientOptions.PHASE, "diff"]) zinggDiff = EZingg(diffArgs, diffOptions) zinggDiff.initAndExecute() ``` diff --git a/docs/running-zingg/create-training-data.md b/docs/running-zingg/create-training-data.md index 136db8da8..2c8024d34 100644 --- a/docs/running-zingg/create-training-data.md +++ b/docs/running-zingg/create-training-data.md @@ -31,10 +31,7 @@ args.setLabelDataSampleSize(0.5) #### Using Python API ```python -options = ClientOptions([ - ClientOptions.PHASE, - "findTrainingData" -]) +options = ClientOptions([ClientOptions.PHASE, "findTrainingData"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -81,10 +78,7 @@ args.setLabelDataSampleSize(0.5) #### Python ```python -options = ClientOptions([ - ClientOptions.PHASE, - "findTrainingData" -]) +options = ClientOptions([ClientOptions.PHASE, "findTrainingData"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` diff --git a/docs/running-zingg/generate-model-documentation.md b/docs/running-zingg/generate-model-documentation.md index adbad7ea1..fc16de823 100644 --- a/docs/running-zingg/generate-model-documentation.md +++ b/docs/running-zingg/generate-model-documentation.md @@ -21,10 +21,7 @@ The documentation is written to the `zinggDir/modelId` folder and can be viewed ### **Python** ```python -options = ClientOptions([ - ClientOptions.PHASE, - "generateDocs" -]) +options = ClientOptions([ClientOptions.PHASE, "generateDocs"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -44,10 +41,7 @@ Enterprise uses `EZingg` instead of `Zingg`. {% endhint %} ```python -options = ClientOptions([ - ClientOptions.PHASE, - "generateDocs" -]) +options = ClientOptions([ClientOptions.PHASE, "generateDocs"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` diff --git a/docs/running-zingg/label-training-pairs.md b/docs/running-zingg/label-training-pairs.md index a82c3b490..ea4e7ac4b 100644 --- a/docs/running-zingg/label-training-pairs.md +++ b/docs/running-zingg/label-training-pairs.md @@ -37,10 +37,7 @@ if the number of matched and unmatched pairs are still not sufficient, then iter ### **Python** ```python -options = ClientOptions([ - ClientOptions.PHASE, - "label" -]) +options = ClientOptions([ClientOptions.PHASE, "label"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -64,10 +61,7 @@ Need to correct a pair you already labeled? See [Update Label](update-label.md). #### Python ```python -options = ClientOptions([ - ClientOptions.PHASE, - "label" -]) +options = ClientOptions([ClientOptions.PHASE, "label"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` diff --git a/docs/running-zingg/link-across-datasets.md b/docs/running-zingg/link-across-datasets.md index 3710b9385..072b4ed14 100644 --- a/docs/running-zingg/link-across-datasets.md +++ b/docs/running-zingg/link-across-datasets.md @@ -31,10 +31,7 @@ Link output contains all input fields plus four Zingg-generated columns: ### Python ```python -options = ClientOptions([ - ClientOptions.PHASE, - "link" -]) +options = ClientOptions([ClientOptions.PHASE, "link"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -65,10 +62,7 @@ display(output) ### Python ```python -options = ClientOptions([ - ClientOptions.PHASE, - "link" -]) +options = ClientOptions([ClientOptions.PHASE, "link"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` diff --git a/docs/running-zingg/quick-start-docker.md b/docs/running-zingg/quick-start-docker.md index 8e917f844..8589d4138 100644 --- a/docs/running-zingg/quick-start-docker.md +++ b/docs/running-zingg/quick-start-docker.md @@ -289,7 +289,7 @@ Zingg scans your dataset using the field rules defined in Step 3 and selects the #### **Python - Community** ```python -options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +options = ClientOptions([ClientOptions.PHASE, "findTrainingData"]) zingg = Zingg(args, options) zingg.initAndExecute() @@ -298,7 +298,7 @@ zingg.initAndExecute() #### **Python - Enterprise** ```python -options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +options = ClientOptions([ClientOptions.PHASE, "findTrainingData"]) zingg = EZingg(args, options) zingg.initAndExecute() @@ -325,7 +325,7 @@ Zingg selects the most informative pairs from your data - not random samples. La #### **Python - Community** ```python -options = ClientOptions([ ClientOptions.PHASE, "label" ]) +options = ClientOptions([ClientOptions.PHASE, "label"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -333,7 +333,7 @@ zingg.initAndExecute() #### **Python - Enterprise** ```python -options = ClientOptions([ ClientOptions.PHASE, "label" ]) +options = ClientOptions([ClientOptions.PHASE, "label"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` @@ -353,7 +353,7 @@ Once you have enough labelled pairs, run the `train` phase. Zingg builds blockin #### **Python - Community** ```python -options = ClientOptions([ ClientOptions.PHASE, "train" ]) +options = ClientOptions([ClientOptions.PHASE, "train"]) zingg = Zingg(args, options) zingg.initAndExecute() @@ -362,7 +362,7 @@ zingg.initAndExecute() #### **Python - Enterprise** ```python -options = ClientOptions([ ClientOptions.PHASE, "train" ]) +options = ClientOptions([ClientOptions.PHASE, "train"]) zingg = EZingg(args, options) zingg.initAndExecute() @@ -381,7 +381,7 @@ Run the `match` phase. Zingg applies the trained models to your full dataset and #### **Python - Community** ```python -options = ClientOptions([ ClientOptions.PHASE, "match" ]) +options = ClientOptions([ClientOptions.PHASE, "match"]) zingg = Zingg(args, options) zingg.initAndExecute() @@ -390,7 +390,7 @@ zingg.initAndExecute() #### **Python - Enterprise** ```python -options = ClientOptions([ ClientOptions.PHASE, "match" ]) +options = ClientOptions([ClientOptions.PHASE, "match"]) zingg = EZingg(args, options) zingg.initAndExecute() diff --git a/docs/running-zingg/reassign-zingg-id.md b/docs/running-zingg/reassign-zingg-id.md index 14581555e..bed26093c 100644 --- a/docs/running-zingg/reassign-zingg-id.md +++ b/docs/running-zingg/reassign-zingg-id.md @@ -144,10 +144,7 @@ Add all field definitions from your new model config - only the structure is sho The new model must produce its own match output before reassign can compare clusters. ```python -options = EClientOptions([ - EClientOptions.PHASE, - "trainMatch" -]) +options = EClientOptions([EClientOptions.PHASE, "trainMatch"]) zinggNew = EZingg(newArgs, options) zinggNew.initAndExecute() ``` @@ -169,10 +166,7 @@ reassignArgs.setResults(reassignOutputPipe) ### Step 5: Execute `reassignZinggId` ```python -reassignOptions = EClientOptions([ - EClientOptions.PHASE, - "reassignZinggId" -]) +reassignOptions = EClientOptions([EClientOptions.PHASE, "reassignZinggId"]) zinggReassign = EZingg(reassignArgs, reassignOptions) zinggReassign.initAndExecute() ``` diff --git a/docs/running-zingg/run-incremental-matching.md b/docs/running-zingg/run-incremental-matching.md index f509d2687..b48ec12e3 100644 --- a/docs/running-zingg/run-incremental-matching.md +++ b/docs/running-zingg/run-incremental-matching.md @@ -161,10 +161,7 @@ incrArgs.setOutputTmp(outputTmpPipe) ### Step 5: Run incremental matching ```python -options = ClientOptions([ - ClientOptions.PHASE, - "runIncremental" -]) +options = ClientOptions([ClientOptions.PHASE, "runIncremental"]) zingg = EZingg(incrArgs, options) zingg.initAndExecute() ``` diff --git a/docs/running-zingg/run-the-match-phase.md b/docs/running-zingg/run-the-match-phase.md index 56f7efb95..d51bf9f3f 100644 --- a/docs/running-zingg/run-the-match-phase.md +++ b/docs/running-zingg/run-the-match-phase.md @@ -22,10 +22,7 @@ Use `match` when you want to find duplicates within a single dataset. If you nee ### Python ```python -options = ClientOptions([ - ClientOptions.PHASE, - "match" -]) +options = ClientOptions([ClientOptions.PHASE, "match"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` diff --git a/docs/running-zingg/update-label.md b/docs/running-zingg/update-label.md index 6f3234c06..717dbaeae 100644 --- a/docs/running-zingg/update-label.md +++ b/docs/running-zingg/update-label.md @@ -19,10 +19,7 @@ Generate model documentation first - see [Generate Model Documentation](generate #### Python ```python -options = ClientOptions([ - ClientOptions.PHASE, - "updateLabel" -]) +options = ClientOptions([ClientOptions.PHASE, "updateLabel"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` diff --git a/docs/running-zingg/verify-blocking.md b/docs/running-zingg/verify-blocking.md index 42284c1d8..6a41b5540 100644 --- a/docs/running-zingg/verify-blocking.md +++ b/docs/running-zingg/verify-blocking.md @@ -113,10 +113,7 @@ Run `verifyBlocking` in two situations: ### Python ```python -options = ClientOptions([ - ClientOptions.PHASE, - "verifyBlocking" -]) +options = ClientOptions([ClientOptions.PHASE, "verifyBlocking"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -132,10 +129,7 @@ zingg.initAndExecute() ### Python ```python -options = ClientOptions([ - ClientOptions.PHASE, - "verifyBlocking" -]) +options = ClientOptions([ClientOptions.PHASE, "verifyBlocking"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` diff --git a/docs/tuning/improve-accuracy/remove-stopwords-optional.md b/docs/tuning/improve-accuracy/remove-stopwords-optional.md index c71ba7b21..5f8ec6be1 100644 --- a/docs/tuning/improve-accuracy/remove-stopwords-optional.md +++ b/docs/tuning/improve-accuracy/remove-stopwords-optional.md @@ -22,7 +22,7 @@ fields where generic terms are common. #### **Python** ```python -options = ClientOptions([ ClientOptions.PHASE, "recommend" ]) +options = ClientOptions([ClientOptions.PHASE, "recommend"]) zingg = Zingg(args, options) zingg.setColumn("") zingg.initAndExecute() @@ -65,7 +65,7 @@ Once you have verified the stopwords, add the `stopWords` path to the relevant f ### **Python** ```python -options = ClientOptions([ ClientOptions.PHASE, "recommend" ]) +options = ClientOptions([ClientOptions.PHASE, "recommend"]) zingg = EZingg(args, options) zingg.setColumn("") zingg.initAndExecute() diff --git a/docs/zingg-python-api/community-python-api.md b/docs/zingg-python-api/community-python-api.md index c863dc370..c41c6bb7f 100644 --- a/docs/zingg-python-api/community-python-api.md +++ b/docs/zingg-python-api/community-python-api.md @@ -133,7 +133,7 @@ Run any phase by passing its name to `ClientOptions`. The same pattern works for #### **Run `findTrainingData`:** ```python -options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +options = ClientOptions([ClientOptions.PHASE, "findTrainingData"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -141,7 +141,7 @@ zingg.initAndExecute() #### **Run `label`:** ```python -options = ClientOptions([ ClientOptions.PHASE, "label" ]) +options = ClientOptions([ClientOptions.PHASE, "label"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -151,7 +151,7 @@ zingg.initAndExecute() Combines `findTrainingData` and `label` into one call — use for smaller datasets where `findTrainingData` runs quickly. ```python -options = ClientOptions([ ClientOptions.PHASE, "findAndLabel" ]) +options = ClientOptions([ClientOptions.PHASE, "findAndLabel"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -161,7 +161,7 @@ zingg.initAndExecute() Revisit and correct previously marked pairs — run `generateDocs` first to identify pairs to update ```python -options = ClientOptions([ ClientOptions.PHASE, "updateLabel" ]) +options = ClientOptions([ClientOptions.PHASE, "updateLabel"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -169,7 +169,7 @@ zingg.initAndExecute() #### **Run `generateDocs`** ```python -options = ClientOptions([ ClientOptions.PHASE, "generateDocs" ]) +options = ClientOptions([ClientOptions.PHASE, "generateDocs"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -177,7 +177,7 @@ zingg.initAndExecute() #### **Run `train`** ```python -options = ClientOptions([ ClientOptions.PHASE, "train" ]) +options = ClientOptions([ClientOptions.PHASE, "train"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -185,7 +185,7 @@ zingg.initAndExecute() #### **Run `match`** ```python -options = ClientOptions([ ClientOptions.PHASE, "match" ]) +options = ClientOptions([ClientOptions.PHASE, "match"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -193,7 +193,7 @@ zingg.initAndExecute() #### **Run `link`** ```python -options = ClientOptions([ ClientOptions.PHASE, "link" ]) +options = ClientOptions([ClientOptions.PHASE, "link"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` @@ -257,10 +257,7 @@ args.setData(inputPipe) outputPipe = CsvPipe("resultFebrl", "/tmp/febrlOutput") args.setOutput(outputPipe) -options = ClientOptions([ - ClientOptions.PHASE, - "match" -]) +options = ClientOptions([ClientOptions.PHASE, "match"]) zingg = Zingg(args, options) zingg.initAndExecute() ``` diff --git a/docs/zingg-python-api/enterprise-zinggec-python-api.md b/docs/zingg-python-api/enterprise-zinggec-python-api.md index 3bea2cf81..af989acdc 100644 --- a/docs/zingg-python-api/enterprise-zinggec-python-api.md +++ b/docs/zingg-python-api/enterprise-zinggec-python-api.md @@ -217,7 +217,7 @@ ZinggEC uses the `EZingg` client. Pattern is the same as Community — set optio #### **Run `findTrainingData`** ```python -options = ClientOptions([ ClientOptions.PHASE, "findTrainingData" ]) +options = ClientOptions([ClientOptions.PHASE, "findTrainingData"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` @@ -225,7 +225,7 @@ zingg.initAndExecute() #### **Run `label`** ```python -options = ClientOptions([ ClientOptions.PHASE, "label" ]) +options = ClientOptions([ClientOptions.PHASE, "label"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` @@ -235,7 +235,7 @@ zingg.initAndExecute() Enterprise convenience — combines `findTrainingData` and `label` ```python -options = ClientOptions([ ClientOptions.PHASE, "findAndLabel" ]) +options = ClientOptions([ClientOptions.PHASE, "findAndLabel"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` @@ -243,7 +243,7 @@ zingg.initAndExecute() #### **Run `generateDocs`** ```python -options = ClientOptions([ ClientOptions.PHASE, "generateDocs" ]) +options = ClientOptions([ClientOptions.PHASE, "generateDocs"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` @@ -251,7 +251,7 @@ zingg.initAndExecute() #### **Run `train`** ```python -options = ClientOptions([ ClientOptions.PHASE, "train" ]) +options = ClientOptions([ClientOptions.PHASE, "train"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` @@ -261,7 +261,7 @@ zingg.initAndExecute() Enterprise convenience — combines `train` and `match` ```python -options = ClientOptions([ ClientOptions.PHASE, "trainMatch" ]) +options = ClientOptions([ClientOptions.PHASE, "trainMatch"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` @@ -269,7 +269,7 @@ zingg.initAndExecute() #### **Run `match`** ```python -options = ClientOptions([ ClientOptions.PHASE, "match" ]) +options = ClientOptions([ClientOptions.PHASE, "match"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` @@ -277,7 +277,7 @@ zingg.initAndExecute() #### **Run `link`** ```python -options = ClientOptions([ ClientOptions.PHASE, "link" ]) +options = ClientOptions([ClientOptions.PHASE, "link"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` @@ -287,7 +287,7 @@ zingg.initAndExecute() Revisit and correct previously marked pairs. ```python -options = ClientOptions([ ClientOptions.PHASE, "updateLabel" ]) +options = ClientOptions([ClientOptions.PHASE, "updateLabel"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` @@ -297,7 +297,7 @@ zingg.initAndExecute() Compare two model outputs. ```python -options = ClientOptions([ ClientOptions.PHASE, "diff" ]) +options = ClientOptions([ClientOptions.PHASE, "diff"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` @@ -342,7 +342,7 @@ incrArgs.setOutputTmp("/tmp/zinggIncrTmp") #### Execute ```python -incrOptions = ClientOptions([ ClientOptions.PHASE, "runIncremental" ]) +incrOptions = ClientOptions([ClientOptions.PHASE, "runIncremental"]) zinggIncr = EZingg(incrArgs, incrOptions) zinggIncr.initAndExecute() ``` @@ -388,7 +388,7 @@ apprArgs.setDestination(destPipe) ```python destPipe = ECsvPipe("approved", "/tmp/approvedClusters") apprArgs.setDestination(destPipe) -apprOptions = ClientOptions([ ClientOptions.PHASE, "approve" ]) +apprOptions = ClientOptions([ClientOptions.PHASE, "approve"]) zinggAppr = EZingg(apprArgs, apprOptions) zinggAppr.initAndExecute() ``` @@ -463,10 +463,7 @@ outputPipe = ECsvPipe("resultFebrl", "/tmp/febrlOutput") outputPipe.setHeader("true") args.setOutput(outputPipe) -options = ClientOptions([ - ClientOptions.PHASE, - "trainMatch" -]) +options = ClientOptions([ClientOptions.PHASE, "trainMatch"]) zingg = EZingg(args, options) zingg.initAndExecute() @@ -476,10 +473,7 @@ incrArgs.setParentArgs(args) incrPipe = ECsvPipe("testFebrlIncr", "examples/febrl/test-incr.csv", schema) incrArgs.setIncrementalData(incrPipe) -incrOptions = ClientOptions([ - ClientOptions.PHASE, - "runIncremental" -]) +incrOptions = ClientOptions([ClientOptions.PHASE, "runIncremental"]) zinggIncr = EZingg(incrArgs, incrOptions) zinggIncr.initAndExecute() ``` diff --git a/docs/zingg-python-api/enterprise-zingges-python-api.md b/docs/zingg-python-api/enterprise-zingges-python-api.md index 52e257b7e..09fdf178a 100644 --- a/docs/zingg-python-api/enterprise-zingges-python-api.md +++ b/docs/zingg-python-api/enterprise-zingges-python-api.md @@ -69,7 +69,7 @@ Use `EZingg` (or `EZinggWithSpark` in notebook environments where a Spark sessio #### **Run `trainMatch`** ```python -options = ClientOptions([ ClientOptions.PHASE, "trainMatch" ]) +options = ClientOptions([ClientOptions.PHASE, "trainMatch"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` @@ -95,7 +95,7 @@ incrArgs.setParentArgs(args) incrPipe = ECsvPipe("testFebrlIncr", "examples/febrl/test-incr.csv", schema) incrArgs.setIncrementalData(incrPipe) -incrOptions = ClientOptions([ ClientOptions.PHASE, "runIncremental" ]) +incrOptions = ClientOptions([ClientOptions.PHASE, "runIncremental"]) zinggIncr = EZingg(incrArgs, incrOptions) zinggIncr.initAndExecute() ``` @@ -146,7 +146,7 @@ outputPipe = ECsvPipe("resultFebrl", "/tmp/febrlOutput") outputPipe.setHeader("true") args.setOutput(outputPipe) -options = ClientOptions([ ClientOptions.PHASE, "trainMatch" ]) +options = ClientOptions([ClientOptions.PHASE, "trainMatch"]) zingg = EZingg(args, options) zingg.initAndExecute() ``` From 95c0b478c58a40eb0c099e8869d907304dc1d521 Mon Sep 17 00:00:00 2001 From: Chandan Date: Tue, 18 Aug 2026 09:26:55 +0530 Subject: [PATCH 074/144] docs: correct hosts file path to /etc/hosts in Ubuntu/WSL2 setup (#1375) Co-authored-by: chandan --- .../ubuntu-wsl2-setup-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing/setting-up-zingg-development-environment/ubuntu-wsl2-setup-guide.md b/docs/contributing/setting-up-zingg-development-environment/ubuntu-wsl2-setup-guide.md index 8e6b97922..ec91123fd 100644 --- a/docs/contributing/setting-up-zingg-development-environment/ubuntu-wsl2-setup-guide.md +++ b/docs/contributing/setting-up-zingg-development-environment/ubuntu-wsl2-setup-guide.md @@ -183,7 +183,7 @@ Replace the `sparkVer` with the version of Spark you installed. For example, **- ### **Step 7: If you have any issue with 'SPARK\_LOCAL\_IP' (Ubuntu)** 1. Install **net-tools** using **sudo apt-get install -y net-tools** -2. Run `ifconfig` in the terminal, find the **IP address** and paste the same in **/opt/hosts** IP address of your Pc-Name +2. Run `ifconfig` in the terminal, find the **IP address**, and add an entry for it in **`/etc/hosts`** mapping the IP to your PC name. ### **Step 8: Run Zingg To Find Training Data** From a1666604693439a23f4c62c09655d33a3641c157 Mon Sep 17 00:00:00 2001 From: Chandan Date: Tue, 18 Aug 2026 09:27:15 +0530 Subject: [PATCH 075/144] docs: fix Cassandra format string to org.apache.spark.sql.cassandra (#1374) Co-authored-by: chandan --- docs/connect-your-data/pipes-and-data-connections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/connect-your-data/pipes-and-data-connections.md b/docs/connect-your-data/pipes-and-data-connections.md index e91f90d16..81564c2c6 100644 --- a/docs/connect-your-data/pipes-and-data-connections.md +++ b/docs/connect-your-data/pipes-and-data-connections.md @@ -148,7 +148,7 @@ See [Install Zingg → Snowflake](../running-zingg/install-zingg.md) for the ful Zingg connects to any datastore that has a Spark connector, plus Snowflake natively in Enterprise. The pipe format string is all that changes. -
DatastoreFormat stringEdition
CSV / TSVcsvAll
ParquetparquetAll
AvroavroAll
JSONjsonAll
Delta tables (Databricks)deltaAll
Unity Catalog (Databricks)deltaAll
OneLake / Fabricabfss:// path + csv or parquetAll
Snowflake (as data source via Spark)net.snowflake.spark.snowflakeAll
Snowflake (native — no Spark cluster)Configured via Snowflake properties file—no pipe classEnterprise
AWS S3csv / parquet via s3a://All
Google Cloud Storagecsv / parquet via gs://All
BigQuerycom.google.cloud.spark.bigqueryAll
PostgreSQL / MySQL / JDBCjdbcAll
CassandraCASSANDRAAll
MongoDBmongoAll
Neo4jorg.neo4j.spark.DataSourceAll
Exasolcom.exasol.sparkAll
Redshiftjdbc (redshift driver)All
+
DatastoreFormat stringEdition
CSV / TSVcsvAll
ParquetparquetAll
AvroavroAll
JSONjsonAll
Delta tables (Databricks)deltaAll
Unity Catalog (Databricks)deltaAll
OneLake / Fabricabfss:// path + csv or parquetAll
Snowflake (as data source via Spark)net.snowflake.spark.snowflakeAll
Snowflake (native — no Spark cluster)Configured via Snowflake properties file—no pipe classEnterprise
AWS S3csv / parquet via s3a://All
Google Cloud Storagecsv / parquet via gs://All
BigQuerycom.google.cloud.spark.bigqueryAll
PostgreSQL / MySQL / JDBCjdbcAll
Cassandraorg.apache.spark.sql.cassandraAll
MongoDBmongoAll
Neo4jorg.neo4j.spark.DataSourceAll
Exasolcom.exasol.sparkAll
Redshiftjdbc (redshift driver)All
{% hint style="success" icon="right-long" %} **Read more**: From 5051d551787c91a0574bf4729f89708215becd67 Mon Sep 17 00:00:00 2001 From: Chandan Date: Tue, 18 Aug 2026 09:27:38 +0530 Subject: [PATCH 076/144] docs: fix invalid match type NULLS_MATCH_NOTHING -> NULL_OR_BLANK (#1373) Co-authored-by: chandan --- docs/zingg-concepts/how-zingg-learns/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/how-zingg-learns/README.md b/docs/zingg-concepts/how-zingg-learns/README.md index 706df0d8f..fdb6d06dd 100644 --- a/docs/zingg-concepts/how-zingg-learns/README.md +++ b/docs/zingg-concepts/how-zingg-learns/README.md @@ -25,7 +25,7 @@ Zingg models understand your data, and are not a generic algorithm applied to it When you define your field configuration, you choose a match type for each field. Here is a starting point for the most common field types. -
Field typeRecommended match type + note
First name, last name,
company name
FUZZY - handles spelling variations
and abbreviations. Use MAPPING (Enterprise) for known
alias or nickname lists.
Email addressEMAIL - matches the portion before the @ only, avoiding mismatches from different domains for the same person.
Date of birth, registration dateEXACT - dates should not have fuzzy tolerance.
Postal code, ZIP codePINCODE - handles common format variants.
Street address (full line)FUZZY - or ONLY_ALPHABETS_FUZZY combined with NUMERIC for the street number as a separate field.
Street number, apartment numberNUMERIC - extracts and compares the number portion only.
National ID, SSN, tax IDEXACT - trusted identifiers should never have fuzzy tolerance. Also consider deterministic matching
(Enterprise) for these fields.
Internal record ID (not used for matching)DONT_USE - appears in output but excluded from comparison.
Fields frequently null across source systemsAdd NULLS_MATCH_NOTHING alongside the main match type.
Product descriptions, free-text notesTEXT - word overlap comparison for longer free-text fields.
+
Field typeRecommended match type + note
First name, last name,
company name
FUZZY - handles spelling variations
and abbreviations. Use MAPPING (Enterprise) for known
alias or nickname lists.
Email addressEMAIL - matches the portion before the @ only, avoiding mismatches from different domains for the same person.
Date of birth, registration dateEXACT - dates should not have fuzzy tolerance.
Postal code, ZIP codePINCODE - handles common format variants.
Street address (full line)FUZZY - or ONLY_ALPHABETS_FUZZY combined with NUMERIC for the street number as a separate field.
Street number, apartment numberNUMERIC - extracts and compares the number portion only.
National ID, SSN, tax IDEXACT - trusted identifiers should never have fuzzy tolerance. Also consider deterministic matching
(Enterprise) for these fields.
Internal record ID (not used for matching)DONT_USE - appears in output but excluded from comparison.
Fields frequently null across source systemsAdd NULL_OR_BLANK alongside the main match type.
Product descriptions, free-text notesTEXT - word overlap comparison for longer free-text fields.
{% hint style="success" icon="right-long" %} **Read more:** From b7d981e837b57242892896b07d01e102baafccf8 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 07:13:15 +0000 Subject: [PATCH 077/144] GITBOOK-352: No subject --- docs/SUMMARY.md | 2 + .../pipes-and-data-connections.md | 16 ++-- docs/zingg-concepts/pipes.md | 9 +++ docs/zingg-concepts/zingg-phases.md | 81 +++++++++++++++++++ 4 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 docs/zingg-concepts/pipes.md create mode 100644 docs/zingg-concepts/zingg-phases.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 145d2391e..e59365a50 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -36,7 +36,9 @@ * [Z Cluster and Zingg ID](zingg-concepts/z-cluster-and-zingg-id.md) * [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) +* [Pipes](zingg-concepts/pipes.md) * [Concept Glossary](zingg-concepts/concept-glossary.md) +* [Zingg Phases](zingg-concepts/zingg-phases.md) ## Running Zingg diff --git a/docs/connect-your-data/pipes-and-data-connections.md b/docs/connect-your-data/pipes-and-data-connections.md index 81564c2c6..febf3b214 100644 --- a/docs/connect-your-data/pipes-and-data-connections.md +++ b/docs/connect-your-data/pipes-and-data-connections.md @@ -21,7 +21,7 @@ Every pipe, whether input or output, has three attributes. You can configure pipes in two ways. Both work in Community and Enterprise. Use whichever fits your workflow. {% tabs %} -{% tab title="Python API" %} +{% tab title="Community" %} Create pipe objects and attach them to your arguments object. ### **Community** @@ -43,12 +43,15 @@ args.setData(inputPipe) #### **Output Pipe** -```python -outputPipe = CsvPipe( - "resultFebrl", - "/tmp/febrlOutput") +
outputPipe = CsvPipe(
+    "resultFebrl",
+    "/tmp/febrlOutput")
 args.setOutput(outputPipe)
-```
+
+{% endtab %} + +{% tab title="Enterprise" %} + ### **Enterprise** @@ -157,3 +160,4 @@ Zingg connects to any datastore that has a Spark connector, plus Snowflake nativ * To configure your pipes step by step as part of your Zingg setup - [Configure Zingg](../running-zingg/configure-zingg.md) * To understand what Zingg adds to your output alongside your input fields (`Z_CLUSTER`, `Z_MINSCORE`, `Zingg ID` and others) - [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) {% endhint %} + diff --git a/docs/zingg-concepts/pipes.md b/docs/zingg-concepts/pipes.md new file mode 100644 index 000000000..65d32bd97 --- /dev/null +++ b/docs/zingg-concepts/pipes.md @@ -0,0 +1,9 @@ +--- +description: Connecting to data +--- + +# Pipes + + + +
DatastoreFormat stringEdition
CSV / TSVcsvAll
ParquetparquetAll
AvroavroAll
JSONjsonAll
Delta tables (Databricks)deltaAll
Unity Catalog (Databricks)deltaAll
OneLake / Fabricabfss:// path + csv or parquetAll
Snowflake (as data source via Spark)net.snowflake.spark.snowflakeAll
Snowflake (native — no Spark cluster)Configured via Snowflake properties file—no pipe classEnterprise
AWS S3csv / parquet via s3a://All
Google Cloud Storagecsv / parquet via gs://All
BigQuerycom.google.cloud.spark.bigqueryAll
PostgreSQL / MySQL / JDBCjdbcAll
Cassandraorg.apache.spark.sql.cassandraAll
MongoDBmongoAll
Neo4jorg.neo4j.spark.DataSourceAll
Exasolcom.exasol.sparkAll
Redshiftjdbc (redshift driver)All
diff --git a/docs/zingg-concepts/zingg-phases.md b/docs/zingg-concepts/zingg-phases.md new file mode 100644 index 000000000..589138215 --- /dev/null +++ b/docs/zingg-concepts/zingg-phases.md @@ -0,0 +1,81 @@ +# Zingg Phases + +
+ +findTrainingData + +Scans your dataset and selects the most informative candidate record pairs for labeling - edge cases where the model has the most to learn. Candidate pairs are written to `UNMARKED_DIR`. + +
+ +
+ +label + +Loads the candidate pairs from `findTrainingData` and presents them in a labeling widget. You mark each pair as Match, No Match, or Uncertain. Labels are saved to `MARKED_DIR`. + +
+ +
+ +train + +Builds the blocking and similarity models from your labeled pairs. Both models are persisted to `zinggDir/modelId`. Once trained, the model can be reused on new data without retraining. + +
+ +
+ +match + +Applies the trained model to your full dataset and writes resolved clusters to the output location. This is the phase that produces deduplicated, entity-resolved output for a single dataset. + +
+ +
+ +link + +Like `match`, but for linking records across two or more separate datasets. Each output record carries a `Z_SOURCE` column indicating which source dataset it came from. Use when you want to find the same entity across datasets without merging them into one. + +
+ +
+ +runIncremental (Enterprise) + +Updates the identity graph with new or changed records without retraining the model. Records that match an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. + +
+ +
+ +explain (Enterprise) + +Shows how a specific cluster was formed, which record pairs were compared, their similarity scores, and how transitive matching connected records through intermediate pairs. Used for governance, audit, and stakeholder review. + +
+ +
+ +generateDocs + +Produces HTML documentation of your labeled training pairs and model statistics. Useful for sharing with subject matter experts before training, or for audit and governance after. + +
+ +
+ +verifyBlocking + +Reports what percentage of your known matching pairs are being blocked together correctly. Run after labeling and before training to catch blocking issues early. + +
+ +
+ +stopwords recommend + +Analyses a specified column and returns a list of high-frequency words that should be treated as stopwords. Optional phase used before training when address or company name fields contain repeated noise words like 'St', 'Ave', 'Inc', 'Ltd'. + +
From a5a666c367996ba1bc36e840b14f193c20439c49 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 11:40:15 +0000 Subject: [PATCH 078/144] GITBOOK-353: No subject --- docs/zingg-concepts/concept-glossary.md | 83 ------------------------- 1 file changed, 83 deletions(-) diff --git a/docs/zingg-concepts/concept-glossary.md b/docs/zingg-concepts/concept-glossary.md index 9f1bf48a5..c772f5784 100644 --- a/docs/zingg-concepts/concept-glossary.md +++ b/docs/zingg-concepts/concept-glossary.md @@ -208,90 +208,7 @@ For example, a retail customer appears in an e-commerce platform, a loyalty prog -### Zingg Phases -Understand the phases that make up the Zingg workflow.\ -Learn how operations such as training, matching, and model management work together to resolve entities from raw data. - -
- -findTrainingData - -Scans your dataset and selects the most informative candidate record pairs for labeling - edge cases where the model has the most to learn. Candidate pairs are written to `UNMARKED_DIR`. - -
- -
- -label - -Loads the candidate pairs from `findTrainingData` and presents them in a labeling widget. You mark each pair as Match, No Match, or Uncertain. Labels are saved to `MARKED_DIR`. - -
- -
- -train - -Builds the blocking and similarity models from your labeled pairs. Both models are persisted to `zinggDir/modelId`. Once trained, the model can be reused on new data without retraining. - -
- -
- -match - -Applies the trained model to your full dataset and writes resolved clusters to the output location. This is the phase that produces deduplicated, entity-resolved output for a single dataset. - -
- -
- -link - -Like `match`, but for linking records across two or more separate datasets. Each output record carries a `Z_SOURCE` column indicating which source dataset it came from. Use when you want to find the same entity across datasets without merging them into one. - -
- -
- -runIncremental (Enterprise) - -Updates the identity graph with new or changed records without retraining the model. Records that match an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. - -
- -
- -explain (Enterprise) - -Shows how a specific cluster was formed, which record pairs were compared, their similarity scores, and how transitive matching connected records through intermediate pairs. Used for governance, audit, and stakeholder review. - -
- -
- -generateDocs - -Produces HTML documentation of your labeled training pairs and model statistics. Useful for sharing with subject matter experts before training, or for audit and governance after. - -
- -
- -verifyBlocking - -Reports what percentage of your known matching pairs are being blocked together correctly. Run after labeling and before training to catch blocking issues early. - -
- -
- -stopwords recommend - -Analyses a specified column and returns a list of high-frequency words that should be treated as stopwords. Optional phase used before training when address or company name fields contain repeated noise words like 'St', 'Ave', 'Inc', 'Ltd'. - -
### Data and Configuration From b42cc38952fbfd07582e4029fa03916c0e9edb65 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 11:49:01 +0000 Subject: [PATCH 079/144] GITBOOK-354: No subject --- docs/SUMMARY.md | 4 ++-- .../platform-guide-for-azure-databricks.md | 4 ++-- .../platform-guide-for-gcp-dataproc.md | 2 +- .../platform-guide-for-microsoft-fabric.md | 2 +- docs/zingg-concepts/concept-glossary.md | 4 ---- docs/zingg-concepts/zingg-phases.md | 12 +++++++++--- docs/zingg-concepts/{pipes.md => zingg-pipes.md} | 6 +++++- 7 files changed, 20 insertions(+), 14 deletions(-) rename docs/zingg-concepts/{pipes.md => zingg-pipes.md} (84%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index e59365a50..de320e74e 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -36,9 +36,9 @@ * [Z Cluster and Zingg ID](zingg-concepts/z-cluster-and-zingg-id.md) * [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) -* [Pipes](zingg-concepts/pipes.md) -* [Concept Glossary](zingg-concepts/concept-glossary.md) +* [Zingg Pipes](zingg-concepts/zingg-pipes.md) * [Zingg Phases](zingg-concepts/zingg-phases.md) +* [Concept Glossary](zingg-concepts/concept-glossary.md) ## Running Zingg diff --git a/docs/platform-guides/platform-guide-for-azure-databricks.md b/docs/platform-guides/platform-guide-for-azure-databricks.md index b1843bdf8..951938975 100644 --- a/docs/platform-guides/platform-guide-for-azure-databricks.md +++ b/docs/platform-guides/platform-guide-for-azure-databricks.md @@ -304,7 +304,7 @@ DOCS_DIR = zinggDir + "/" + modelId + "/docs/" displayHTML(open(DOCS_DIR + "model.html", 'r').read()) ``` -_**IMAGE TO BE ADDED —**** ****`generateDocs`**** ****HTML output rendered inside a Databricks notebook showing labeled pair examples. Tanwi to check with team for screenshot from a live notebook run. Even a small portion of the rendered HTML is sufficient — it tells the reader what to expect before they run it. Place: below the**** ****`displayHTML`****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*line.**_ +_**IMAGE TO BE ADDED —**** ****`generateDocs`**** ****HTML output rendered inside a Databricks notebook showing labeled pair examples. Tanwi to check with team for screenshot from a live notebook run. Even a small portion of the rendered HTML is sufficient — it tells the reader what to expect before they run it. Place: below the**** ****`displayHTML`****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*line.**_ {% endtab %} {% tab title="Enterprise" %} @@ -626,7 +626,7 @@ data_html = "\n".join(r.value for r in data_doc.collect()) displayHTML(data_html) ``` -_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*output rendered inside a Databricks notebook showing labeled pair examples in HTML. Tanwi to check with team for screenshot from a live notebook run. Can reuse the OS version if the output looks the same.**_ +_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*output rendered inside a Databricks notebook showing labeled pair examples in HTML. Tanwi to check with team for screenshot from a live notebook run. Can reuse the OS version if the output looks the same.**_ ### Notebook 05: Train and match diff --git a/docs/platform-guides/platform-guide-for-gcp-dataproc.md b/docs/platform-guides/platform-guide-for-gcp-dataproc.md index ca33a07a4..b1c2961b4 100644 --- a/docs/platform-guides/platform-guide-for-gcp-dataproc.md +++ b/docs/platform-guides/platform-guide-for-gcp-dataproc.md @@ -411,7 +411,7 @@ with open(DOCS_DIR + "data.html", 'r') as f: display(HTML(f.read())) ``` -_**IMAGE TO BE ADDED—**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*HTML output rendered inside JupyterLab on Dataproc showing labeled pair examples. Source: not in the GCP docx. Tanwi to check with team for screenshot from a live notebook run.**_ +_**IMAGE TO BE ADDED—**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*HTML output rendered inside JupyterLab on Dataproc showing labeled pair examples. Source: not in the GCP docx. Tanwi to check with team for screenshot from a live notebook run.**_ {% hint style="success" icon="right-long" %} `generateDocs` is optional. Skip it if you have 30–40 matches and 30–40 non-matches and are confident in your labeling quality. diff --git a/docs/platform-guides/platform-guide-for-microsoft-fabric.md b/docs/platform-guides/platform-guide-for-microsoft-fabric.md index f29e89ca0..494e1360e 100644 --- a/docs/platform-guides/platform-guide-for-microsoft-fabric.md +++ b/docs/platform-guides/platform-guide-for-microsoft-fabric.md @@ -414,7 +414,7 @@ displayHTML(open(DOCS_DIR + "model.html", 'r').read()) displayHTML(open(DOCS_DIR + "data.html", 'r').read()) ``` -_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*HTML output rendered inside a Fabric notebook showing labeled pair examples in a table. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ +_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*HTML output rendered inside a Fabric notebook showing labeled pair examples in a table. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ ### Notebook 04: Train and match diff --git a/docs/zingg-concepts/concept-glossary.md b/docs/zingg-concepts/concept-glossary.md index c772f5784..679895c37 100644 --- a/docs/zingg-concepts/concept-glossary.md +++ b/docs/zingg-concepts/concept-glossary.md @@ -219,11 +219,7 @@ Explore the settings, inputs, and preprocessing concepts that determine how reco Pipes -Zingg's abstraction for connecting to data. A pipe encapsulates the source or destination of records; a CSV file path, a Snowflake table, a BigQuery dataset, a Cassandra keyspace. `CsvPipe`, `SnowflakePipe`, `BigQueryPipe`, and the generic `Pipe` class are the main pipe types in Community. Enterprise adds `ECsvPipe` and `UCPipe`. -{% hint style="success" icon="right-long" %} -**Read more**: [Pipes and Data Connections](../connect-your-data/pipes-and-data-connections.md) -{% endhint %} diff --git a/docs/zingg-concepts/zingg-phases.md b/docs/zingg-concepts/zingg-phases.md index 589138215..7abc49713 100644 --- a/docs/zingg-concepts/zingg-phases.md +++ b/docs/zingg-concepts/zingg-phases.md @@ -1,10 +1,16 @@ +--- +description: Steps in the entity resolution pipeline +--- + # Zingg Phases +Zingg programs run different aspects of the entity resolution pipeline. Each aspect covers one aspect of the pipeline. Here are the phases Zingg defines. +
findTrainingData -Scans your dataset and selects the most informative candidate record pairs for labeling - edge cases where the model has the most to learn. Candidate pairs are written to `UNMARKED_DIR`. +Scans your dataset and selects the most informative candidate record pairs for labeling - edge cases where the model has the most to learn. Candidate pairs are written within the warehouse/lakehouse based on your configuration.
@@ -12,7 +18,7 @@ Scans your dataset and selects the most informative candidate record pairs for l label -Loads the candidate pairs from `findTrainingData` and presents them in a labeling widget. You mark each pair as Match, No Match, or Uncertain. Labels are saved to `MARKED_DIR`. +Loads the candidate pairs from `findTrainingData` and presents them in a labeling widget. You mark each pair as Match, No Match, or Uncertain. @@ -20,7 +26,7 @@ Loads the candidate pairs from `findTrainingData` and presents them in a labelin train -Builds the blocking and similarity models from your labeled pairs. Both models are persisted to `zinggDir/modelId`. Once trained, the model can be reused on new data without retraining. +Builds the blocking and similarity models from your labeled pairs. Both models are persisted to the customer environment. Once trained, the model can be reused on new data without retraining. diff --git a/docs/zingg-concepts/pipes.md b/docs/zingg-concepts/zingg-pipes.md similarity index 84% rename from docs/zingg-concepts/pipes.md rename to docs/zingg-concepts/zingg-pipes.md index 65d32bd97..dd2e28d1e 100644 --- a/docs/zingg-concepts/pipes.md +++ b/docs/zingg-concepts/zingg-pipes.md @@ -2,8 +2,12 @@ description: Connecting to data --- -# Pipes +# Zingg Pipes +Zingg's abstraction for connecting to data. A pipe encapsulates the source or destination of records; a delta file path, a Snowflake table, a UC table, an RDBMS dataset. Different pipes are configured by passing the format string in the configuration. +{% hint style="success" icon="right-long" %} +**Read more**: [Pipes and Data Connections](../connect-your-data/pipes-and-data-connections.md) +{% endhint %}
DatastoreFormat stringEdition
CSV / TSVcsvAll
ParquetparquetAll
AvroavroAll
JSONjsonAll
Delta tables (Databricks)deltaAll
Unity Catalog (Databricks)deltaAll
OneLake / Fabricabfss:// path + csv or parquetAll
Snowflake (as data source via Spark)net.snowflake.spark.snowflakeAll
Snowflake (native — no Spark cluster)Configured via Snowflake properties file—no pipe classEnterprise
AWS S3csv / parquet via s3a://All
Google Cloud Storagecsv / parquet via gs://All
BigQuerycom.google.cloud.spark.bigqueryAll
PostgreSQL / MySQL / JDBCjdbcAll
Cassandraorg.apache.spark.sql.cassandraAll
MongoDBmongoAll
Neo4jorg.neo4j.spark.DataSourceAll
Exasolcom.exasol.sparkAll
Redshiftjdbc (redshift driver)All
From 9c7a40f6735134bc1ee4978721d83d5e643dd2af Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 11:55:38 +0000 Subject: [PATCH 080/144] GITBOOK-355: No subject --- docs/SUMMARY.md | 1 + .../platform-infrastructure-vs-data-platform.md | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 docs/zingg-concepts/platform-infrastructure-vs-data-platform.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index de320e74e..87054a9d6 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -39,6 +39,7 @@ * [Zingg Pipes](zingg-concepts/zingg-pipes.md) * [Zingg Phases](zingg-concepts/zingg-phases.md) * [Concept Glossary](zingg-concepts/concept-glossary.md) +* [Platform Infrastructure vs Data Platform](zingg-concepts/platform-infrastructure-vs-data-platform.md) ## Running Zingg diff --git a/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md b/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md new file mode 100644 index 000000000..3e4151a10 --- /dev/null +++ b/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md @@ -0,0 +1,13 @@ +--- +description: Where Zingg runs, which data is resolves +--- + +# Platform Infrastructure vs Data Platform + +Two things that are easy to conflate but mean different things in Zingg's context. Platform infrastructure is where computation happens, such as the Databricks/Fabric/EMR/Glue/Dataproc Spark service or Snowflake compute that processes your data. A data platform is where your data lives - Snowflake tables, Databricks Lakehouse and Unity Catalog, BigQuery, Redshift, or a file store like S3. + +These are independent: you can have your data in Snowflake but run Zingg's computation on Spark. Zingg connects to the data platform and runs computations on whichever engine you configure. + +{% hint style="success" icon="right-long" %} +**Read more:** [Connect data](../connect-your-data/pipes-and-data-connections.md) | [Install Zingg](../running-zingg/install-zingg.md) +{% endhint %} From 5061e6276578a9f1928a397e4f344576eaffb9ca Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 12:00:51 +0000 Subject: [PATCH 081/144] GITBOOK-356: No subject --- docs/SUMMARY.md | 8 +++- .../standardize-fields-and-results.md | 6 +-- .../tuning/configure-field-standardization.md | 2 +- docs/zingg-concepts/concept-glossary.md | 39 +------------------ .../README.md => identity-graph.md} | 8 ++-- docs/zingg-concepts/the-graph-algorithm.md | 2 +- docs/zingg-concepts/z-cluster-and-zingg-id.md | 2 +- .../README.md | 2 +- .../incremental-flow.md | 14 +++++++ 9 files changed, 32 insertions(+), 51 deletions(-) rename docs/{zingg-concepts/identity-graph => }/standardize-fields-and-results.md (92%) rename docs/zingg-concepts/{identity-graph/README.md => identity-graph.md} (92%) create mode 100644 docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 87054a9d6..508fa0b34 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -9,6 +9,7 @@ * [The problems Zingg is built to solve](zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md) * [Deterministic vs Probabilistic Matching](zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) * [Deduplication, linking and resolving](zingg-concepts/zingg-entity-resolution-platform/deduplication-linking-and-resolving.md) + * [Incremental Flow](zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md) * [How Zingg Learns](zingg-concepts/how-zingg-learns/README.md) * [Zingg Active Learning](zingg-concepts/how-zingg-learns/zingg-active-learning.md) * [Zingg Models](zingg-concepts/how-zingg-learns/zingg-models/README.md) @@ -31,8 +32,7 @@ * [DONT\_USE Match](zingg-concepts/how-zingg-learns/match-types/dont_use-match.md) * [MAPPING\_(FILENAME) Match](zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md) * [The graph algorithm](zingg-concepts/the-graph-algorithm.md) -* [Identity Graph](zingg-concepts/identity-graph/README.md) - * [Standardize Fields and Results](zingg-concepts/identity-graph/standardize-fields-and-results.md) +* [Identity Graph](zingg-concepts/identity-graph.md) * [Z Cluster and Zingg ID](zingg-concepts/z-cluster-and-zingg-id.md) * [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) @@ -64,6 +64,10 @@ * [Compare Model Results](running-zingg/compare-model-results.md) * [Knowledge Graph](running-zingg/knowledge-graph.md) +*** + +* [Standardize Fields and Results](standardize-fields-and-results.md) + ## Connect Your Data * [Pipes and Data Connections](connect-your-data/pipes-and-data-connections.md) diff --git a/docs/zingg-concepts/identity-graph/standardize-fields-and-results.md b/docs/standardize-fields-and-results.md similarity index 92% rename from docs/zingg-concepts/identity-graph/standardize-fields-and-results.md rename to docs/standardize-fields-and-results.md index 3898e2d5d..1b1f40012 100644 --- a/docs/zingg-concepts/identity-graph/standardize-fields-and-results.md +++ b/docs/standardize-fields-and-results.md @@ -60,7 +60,7 @@ Enforce canonical values on classification fields like industry codes, status la {% hint style="success" icon="right-long" %} **Read more**: -* To create a mapping file and configure the postprocessor on a field → [Configure Field Standardization](../../tuning/configure-field-standardization.md) -* For the full field definition configuration including all `EFieldDefinition` methods - [Configure Zingg](../../running-zingg/configure-zingg.md) -* For dictionary-based matching using the MAPPING match type (nicknames, aliases, company names) - [Match Types](../how-zingg-learns/match-types/) +* To create a mapping file and configure the postprocessor on a field → [Configure Field Standardization](tuning/configure-field-standardization.md) +* For the full field definition configuration including all `EFieldDefinition` methods - [Configure Zingg](running-zingg/configure-zingg.md) +* For dictionary-based matching using the MAPPING match type (nicknames, aliases, company names) - [Match Types](zingg-concepts/how-zingg-learns/match-types/) {% endhint %} diff --git a/docs/tuning/configure-field-standardization.md b/docs/tuning/configure-field-standardization.md index 9bfdebcb3..770010e12 100644 --- a/docs/tuning/configure-field-standardization.md +++ b/docs/tuning/configure-field-standardization.md @@ -129,7 +129,7 @@ If the output still shows raw variants: {% hint style="success" icon="right-long" %} **Read more**: -* [Standardize Fields and Results](../zingg-concepts/identity-graph/standardize-fields-and-results.md) - concept and where it is useful +* [Standardize Fields and Results](../standardize-fields-and-results.md) - concept and where it is useful * [Configure Zingg](../running-zingg/configure-zingg.md) - full field definition configuration including all `EFieldDefinition` methods * [Match Types](../zingg-concepts/how-zingg-learns/match-types/) - for the MAPPING match type, which uses a similar mapping file approach for matching {% endhint %} diff --git a/docs/zingg-concepts/concept-glossary.md b/docs/zingg-concepts/concept-glossary.md index 679895c37..a87b3eddd 100644 --- a/docs/zingg-concepts/concept-glossary.md +++ b/docs/zingg-concepts/concept-glossary.md @@ -175,21 +175,7 @@ Zingg's underlying data structure representing resolved entities and the relatio * In Enterprise, it is persistent and updated incrementally: new and changed records are incorporated without a full re-run, and Zingg IDs serve as stable node identifiers within it. {% hint style="success" icon="right-long" %} -**Read more:** [Identity graph](identity-graph/) | [Run incremental matching](../running-zingg/run-incremental-matching.md) -{% endhint %} - - - -
- -Incremental flow - -The process of updating the identity graph with new, changed, or deleted records without re-running the full match across the entire dataset. Zingg incorporates incoming records into existing clusters, handles cluster merges and unmerges automatically, generates new Zingg IDs for records that do not match any existing cluster, and preserves human-approved decisions so they are not overridden. - -Many tools claim incremental capability but cannot maintain match quality and stable IDs simultaneously. This feature is a key Zingg Enterprise differentiator. _Enterprise only_. - -{% hint style="success" icon="right-long" %} -**Read more:** [Run incremental matching](../running-zingg/run-incremental-matching.md) +**Read more:** [Identity graph](identity-graph.md) | [Run incremental matching](../running-zingg/run-incremental-matching.md) {% endhint %}
@@ -217,14 +203,6 @@ Explore the settings, inputs, and preprocessing concepts that determine how reco
-Pipes - - - -
- -
- Pass Through An Enterprise feature for records that are partially populated, outdated, or not suitable for matching, but which still need to appear in the output for governance or compliance purposes. `Pass Through` records are excluded from matching, linking, and incremental processes. Each pass through the record receives its own unique `Zingg ID` in the output. @@ -259,21 +237,6 @@ also supports Snowflake native compute, which requires no Spark cluster at all.
-Cloud infrastructure vs data platform - -Two things that are easy to conflate but mean different things in Zingg's context. Cloud infrastructure is where computation happens, such as the Spark service or Snowflake compute that processes your data. A data platform is where your data lives -Snowflake.\ -Databricks, BigQuery, Redshift, or a file store like S3. - -These are independent: you can have your data in Snowflake but run Zingg's computation on Spark. Zingg connects to the data platform and runs computations on whichever engine you configure. - -{% hint style="success" icon="right-long" %} -**Read more:** [Connect data](../connect-your-data/pipes-and-data-connections.md) | [Install Zingg](../running-zingg/install-zingg.md) -{% endhint %} - -
- -
- Python API vs CLI The two ways to invoke Zingg. The Python API is the most widely adopted method; you call the Zingg phases as Python functions from a notebook or script. The CLI invokes Zingg phases from the command line using a JSON config file. diff --git a/docs/zingg-concepts/identity-graph/README.md b/docs/zingg-concepts/identity-graph.md similarity index 92% rename from docs/zingg-concepts/identity-graph/README.md rename to docs/zingg-concepts/identity-graph.md index 89f6c4adc..d6510f17c 100644 --- a/docs/zingg-concepts/identity-graph/README.md +++ b/docs/zingg-concepts/identity-graph.md @@ -43,8 +43,8 @@ Enterprise gives you the same graph with stable `Zingg IDs` , so the graph can g **Read more**: -* [Z Cluster vs Zingg ID](../z-cluster-and-zingg-id.md) -* [Zingg Models](../how-zingg-learns/zingg-models/) -* [Run incremental matching](../../running-zingg/run-incremental-matching.md) -* [Community Vs Enterprise](../community-vs-enterprise/) +* [Z Cluster vs Zingg ID](z-cluster-and-zingg-id.md) +* [Zingg Models](how-zingg-learns/zingg-models/) +* [Run incremental matching](../running-zingg/run-incremental-matching.md) +* [Community Vs Enterprise](community-vs-enterprise/) {% endhint %} diff --git a/docs/zingg-concepts/the-graph-algorithm.md b/docs/zingg-concepts/the-graph-algorithm.md index aabf4c888..618620641 100644 --- a/docs/zingg-concepts/the-graph-algorithm.md +++ b/docs/zingg-concepts/the-graph-algorithm.md @@ -15,7 +15,7 @@ In Community, clusters are assigned a `Z Cluster` that is non-persistent between {% hint style="success" icon="right-long" %} **Read more**: -* [Identity Graph](identity-graph/) +* [Identity Graph](identity-graph.md) * [Z Cluster ID vs Zingg ID](z-cluster-and-zingg-id.md) * [Zingg ID](/broken/pages/9QpDFW20AMt0UJ4cEW6b) {% endhint %} diff --git a/docs/zingg-concepts/z-cluster-and-zingg-id.md b/docs/zingg-concepts/z-cluster-and-zingg-id.md index fa4765232..e153bc3be 100644 --- a/docs/zingg-concepts/z-cluster-and-zingg-id.md +++ b/docs/zingg-concepts/z-cluster-and-zingg-id.md @@ -62,7 +62,7 @@ Use this column to: **Read more:** * [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) - for the full reference of every Zingg output column -* [Identity Graph](identity-graph/) - how Zingg's resolved entities form a graph structure +* [Identity Graph](identity-graph.md) - how Zingg's resolved entities form a graph structure * [Run Incremental Matching](../running-zingg/run-incremental-matching.md) - how Zingg IDs persist when new records arrive * [Reassign Zingg ID](../running-zingg/reassign-zingg-id.md) - preserve Zingg IDs across model retraining or platform migration * [Concept Glossary](concept-glossary.md) - every Zingg term in one place diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/README.md b/docs/zingg-concepts/zingg-entity-resolution-platform/README.md index 5d3fe289c..429899581 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/README.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/README.md @@ -4,7 +4,7 @@ Zingg is an ML-powered entity resolution engine built to run where your data alr Three capabilities work together to handle the full problem: -
Warehouse-native executionProbabilistic + deterministic matchingPersistent identity graph
Zingg runs inside Databricks, Microsoft
Fabric, Snowflake, GCP Dataproc, AWS Glue, and AWS EMR. Your data never
leaves your environment. No ETL pipelines. No external APIs. There is no separate infrastructure to operate. The same model that runs on 100,000 records scales to hundreds of millions using your existing Spark or Snowflake compute, without any architectural changes.

Probabilistic matching is Zingg's default and available in the Community Edition. The ML model learns from your labeled pairs; 30 to 50 examples are enough to build a model calibrated
to your specific data and scores every candidate pair on multiple field-level features. It handles
typos, abbreviations, missing values, and format variations automatically.

Deterministic matching (Enterprise) adds hard rules for trusted identifiers.
When two records share the same SSN, tax ID, or email, Zingg treats them
as the same entity without consulting the ML model.

Both approaches run in a single flow, finding all possible matches.

Deterministic vs Probabilistic
Matching

Every resolved entity receives a Zingg ID, a globally unique, persistent GUID assigned in Enterprise that remains stable
across runs, incremental updates, and model changes.

Community produces a Z Cluster
which is unique. Matching records share the same Z_Cluster that is reassigned each run.


Enterprise produces a Zingg ID you can store in downstream systems
with confidence.

The identity graph grows incrementally. New records are matched to existing clusters without rerunning on your full dataset.

Identity Graph
Z Cluster and Zingg ID

+
Warehouse-native executionProbabilistic + deterministic matchingPersistent identity graph
Zingg runs inside Databricks, Microsoft
Fabric, Snowflake, GCP Dataproc, AWS Glue, and AWS EMR. Your data never
leaves your environment. No ETL pipelines. No external APIs. There is no separate infrastructure to operate. The same model that runs on 100,000 records scales to hundreds of millions using your existing Spark or Snowflake compute, without any architectural changes.

Probabilistic matching is Zingg's default and available in the Community Edition. The ML model learns from your labeled pairs; 30 to 50 examples are enough to build a model calibrated
to your specific data and scores every candidate pair on multiple field-level features. It handles
typos, abbreviations, missing values, and format variations automatically.

Deterministic matching (Enterprise) adds hard rules for trusted identifiers.
When two records share the same SSN, tax ID, or email, Zingg treats them
as the same entity without consulting the ML model.

Both approaches run in a single flow, finding all possible matches.

Deterministic vs Probabilistic
Matching

Every resolved entity receives a Zingg ID, a globally unique, persistent GUID assigned in Enterprise that remains stable
across runs, incremental updates, and model changes.

Community produces a Z Cluster
which is unique. Matching records share the same Z_Cluster that is reassigned each run.


Enterprise produces a Zingg ID you can store in downstream systems
with confidence.

The identity graph grows incrementally. New records are matched to existing clusters without rerunning on your full dataset.

Identity Graph
Z Cluster and Zingg ID

This is the combination that makes enterprise-scale entity resolution computationally feasible and practically maintainable. diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md b/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md new file mode 100644 index 000000000..9cc60cd3f --- /dev/null +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md @@ -0,0 +1,14 @@ +--- +description: Continuously updated identity graph +--- + +# Incremental Flow + +The process of updating the identity graph with new, changed, or deleted records without re-running the full match across the entire dataset is known as incremental flow in Zingg. Zingg incorporates incoming records into existing clusters, handles cluster merges and unmerges automatically, generates new Zingg IDs for records that do not match any existing cluster, and preserves human-approved decisions so they are not overridden. + +Many tools claim incremental capability but cannot maintain match quality and stable IDs simultaneously. This feature is a key Zingg Enterprise differentiator. _Enterprise only_. + +{% hint style="success" icon="right-long" %} +**Read more:** [Run incremental matching](../../running-zingg/run-incremental-matching.md) +{% endhint %} + From 06aa7288fbc3d7c534fe6851776ee9a4ad897a5c Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 12:17:33 +0000 Subject: [PATCH 082/144] GITBOOK-357: No subject --- docs/SUMMARY.md | 2 +- docs/zingg-concepts/concept-glossary.md | 18 +++- .../how-zingg-learns/match-types/README.md | 82 +------------------ 3 files changed, 18 insertions(+), 84 deletions(-) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 508fa0b34..c2fc9f84b 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -38,7 +38,7 @@ * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) * [Zingg Pipes](zingg-concepts/zingg-pipes.md) * [Zingg Phases](zingg-concepts/zingg-phases.md) -* [Concept Glossary](zingg-concepts/concept-glossary.md) +* [Frequently Asked Questions](zingg-concepts/concept-glossary.md) * [Platform Infrastructure vs Data Platform](zingg-concepts/platform-infrastructure-vs-data-platform.md) ## Running Zingg diff --git a/docs/zingg-concepts/concept-glossary.md b/docs/zingg-concepts/concept-glossary.md index a87b3eddd..f22042b35 100644 --- a/docs/zingg-concepts/concept-glossary.md +++ b/docs/zingg-concepts/concept-glossary.md @@ -4,7 +4,7 @@ description: >- term to expand. --- -# Concept Glossary +# Frequently Asked Questions {% hint style="success" icon="right-long" %} New to Zingg? Start here. Understanding these terms will make every other page easier to follow. @@ -92,12 +92,24 @@ Learn how field definitions, match types, and related settings influence matchin
-Match types +Which Match types should I use? -The similarity function assigned to each field, telling Zingg how to compare values in that field across records. Zingg provides match types for fuzzy comparison, exact match, email, numeric, text, and several specialized types for alphabet-only or null handling. +Match types are the combination of similarity functions assigned to each field, telling Zingg how to compare values in that field across records. Zingg provides match types for fuzzy comparison, exact match, email, numeric, text, and several specialized types for alphabet-only or null handling. Each field receives one match type. The full list of all match types; what they do, when to use each, and examples on [Match Types](how-zingg-learns/match-types/). +A quick reference for the most common fields: + +* **Name fields (first name, last name, company name)** → `FUZZY`. Handles spelling variations and abbreviations. Use MAPPING for known alias or nickname lists (Enterprise only). +* **Email address** → `EMAIL`. Matches before the `@` ; only avoids mismatches from different email domains for the same person. +* **Date of birth / registration date** → `EXACT`. Dates should not have fuzzy tolerance. +* **Postal / ZIP code** → `PINCODE`. Handles common format variants. +* **Street address (full address line)** → `FUZZY` or `ONLY_ALPHABETS_FUZZY` combined with NUMERIC for the street number as a separate field. +* **Street number / apartment number** → `NUMERIC`. Extracts and compares the number portion only. +* **Internal record ID (not used for matching)** → `DONT_USE`. Appears in output but excluded from comparison. +* **Any field frequently null across source systems** → Add `NULL_OR_BLANK` alongside the main match type. +* **Product descriptions / notes** → TEXT. `Word` overlap comparison for longer free-text fields. + {% hint style="success" icon="right-long" %} **Read more:** [Match types](concept-glossary.md#match-types) reference | [Configure Zingg](../running-zingg/configure-zingg.md) {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/README.md b/docs/zingg-concepts/how-zingg-learns/match-types/README.md index f6d2d2ab1..e98f45d99 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/README.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/README.md @@ -7,97 +7,19 @@ description: >- # Match Types Every field in your Zingg configuration gets a match type assigned to it. The match type tells\ -Zingg which similarity function to apply when comparing values in that field across two records.\ +Zingg which similarity function to apply when comparing values in that field across two records. Each `dataType` has predefined features for the given `matchType`. For example, the `string` type with `FUZZY` match is configured for Affine and Jaro-Winkler string comparison. + Choosing the right match type for each field is one of the highest-impact configuration decisions you make as it directly affects matching accuracy. Multiple match types can be assigned to a single field, separated by commas.
Match TypeWhat it doesData typesBest forEditionExample
FUZZYBroad matching tolerant to typos,
abbreviations and variations. Uses fuzzy string similarity features.
string, integer, long, double, dateNames, addresses, free-text fieldsAll"Jon Smith", "Jonathan Smith", and
"J. Smith" all match the same record.
FUZZY_OPTIMISEDSame accuracy as FUZZY, approximately 2X faster. Lower CPU and memory usage on large datasets. Use this in production when FUZZY accuracy is needed at scalestring, integer, long, double, dateSame as FUZZY - use when dataset
is large and performance matters
Enterprise onlySame matching behaviour as FUZZY. Processes a 10M-record dataset in approximately half the time.
EXACTProvides an exact-match signal to the classifier. A strong indicator when two values align perfectly. Records may still match based on other fields and how training pairs were labeled.string, integer, long, date, booleanCountry codes, boolean flags,
categorical fields where no
variation is expected
All"US" and "US"—strong match signal. "US" and ""us"—no exact signal, but the record pair may still match if other fields score highly.
EMAILMatches only the local part before
the @ - ignores the domain entirely.
stringEmail address fieldsAll"john.smith@gmail.com" and
"john.smith@company.com" — match.
"john.smith@gmail.com" and
"jsmith@gmail.com" — no match.
EMAIL_OPTIMISEDSame as EMAIL but significantly faster
evaluation on large datasets.
stringEmail fields at production scaleEnterprise onlySame behavior as EMAIL.
Use when comparing millions of
email addresses.
PINCODEMatches postal and pin codes across
common format variants (e.g. xxxxx
and xxxxx-xxxx).
stringPostal/ZIP code fieldsAll"94102" and "94102-1234" — match.
"94102" and "94103" — no match.
NUMERICExtracts numbers from strings and
compares how many are the same
across both strings.
stringStreet numbers, apartment numbers,
building numbers
All"42B Main St" and "42 Main Street" —
the number 42 is extracted and matched
from both. Alphabetic variation ignored.
NUMERIC_WITH_UNITSExtracts product codes or numbers
with units (e.g. 16gb, 500ml) and
compares how many are the same.
stringProduct specification fields,
size field
All16GB" and "16 GB DDR4" — the 16 and "GB" tokens match. "16gb" and "32gb" — the units match but the numbers do not.
TEXTCompares overlapping words between two strings. Good for longer descriptive text without many typos.stringProduct descriptions, notes,
long free-text fields
All"Enterprise data management platform"
and "data management platform for
enterprise" — high overlap, likely match.
"Enterprise software" and
"consumer hardware" — low overlap,
likely no match.
ONLY_ALPHABETS_EXACTIgnores all numbers, then requires an exact letter match.stringBuilding names where unit numbers
should be ignored
All"Tower A, Flat 12" and "Tower A, Flat 7"
— "Tower A" matches exactly after
numbers are stripped.
"Tower A" and "Tower B" — no match.
ONLY_ALPHABETS_FUZZYIgnores all numbers, then applies fuzzy matching to alphabetic characters onlystringAddress fields where street names
need fuzzy matching and numbers
are handled separately via NUMERIC
All"St. James Rd" and "Saint James Road" —
alphabetic characters compared fuzzily.
Combine with NUMERIC for the
street number.
ONLY_ALPHABETS_FUZZY_OPTIMISEDSame as ONLY_ALPHABETS_FUZZY but optimised for production scale.stringSame as ONLY_ALPHABETS_FUZZY -use when dataset is largeEnterprise onlySame matching behaviour.
Use for large address datasets.
NULL_OR_BLANKBy default Zingg treats nulls as
matches. Add this alongside another
match type to build an explicit feature for null/blank values so the model can learn their effect.
string, integer, long, date, booleanAny field frequently null or blank
across source systems -combine
with FUZZY or EXACT
AllField A: null, Field B: "John" — with NULL_OR_BLANK added, these are NOT treated as matching. Without it, null matches anything.
DONT_USEAppears in output but no computation
is done. Not shown in the labeller
when showConcise is true.
anyRecord IDs, internal keys,
any field needed in output
but not in matching
AllA customer_id field that must appear
in output for traceability but should
not influence whether two records
are the same entity.
MAPPING_(FILENAME)Maps input field values to canonical
values using a user-supplied lookup file. Handles nicknames, abbreviations,
company name variants, and categorical
normalisation. Matching is tolerant
to common variations defined in the mapping file.
stringName fields (nicknames), company
fields, categorical fields with
different representations across
systems
Enterprise only"Jon", "Jonathan", "Johnny" all map
to canonical "John" via nicknames.json.
"M", "Male", "1" all map to "M"
via gender.json. Matched before
similarity scoring runs.
-{% tabs %} -{% tab title="Python" %} -{% code overflow="wrap" %} -```python -from zingg.client import * -from zingg.pipes import * - -#Community -fname = FieldDefinition("fname", "string", MatchType.FUZZY) -lname = FieldDefinition("lname", "string", MatchType.FUZZY) - -stNo = FieldDefinition("stNo", "string", MatchType.FUZZY) -add1 = FieldDefinition("add1", "string", MatchType.FUZZY) - -dob = FieldDefinition("dob", "string", MatchType.EXACT) -ssn = FieldDefinition("ssn", "string", MatchType.EXACT) - -fieldDefs = [ fname, lname, stNo, add1, dob, ssn ] -args.setFieldDefinition(fieldDefs) -``` -{% endcode %} -{% endtab %} - -{% tab title="JSON" %} -```json -{ - "fieldDefinition" : [ - { - "fieldName" : "fname", - "matchType" : "fuzzy", - "fields" : "fname", - "dataType" : "string" - }, - { - "fieldName" : "lname", - "matchType" : "fuzzy", - "fields" : "lname", - "dataType" : "string" - }, - { - "fieldName" : "stNo", - "matchType" : "fuzzy", - "fields" : "stNo", - "dataType" : "string" - }, - { - "fieldName" : "add1", - "matchType" : "fuzzy", - "fields" : "add1", - "dataType" : "string" - }, - { - "fieldName" : "dob", - "matchType" : "exact", - "fields" : "dob", - "dataType" : "string" - }, - { - "fieldName" : "ssn", - "matchType" : "exact", - "fields" : "ssn", - "dataType" : "string" - } - ] -} -``` -{% endtab %} -{% endtabs %} -
Which match type should I use for each field type? -A quick reference for the most common fields: -* **Name fields (first name, last name, company name)** → `FUZZY`. Handles spelling variations and abbreviations. Use MAPPING for known alias or nickname lists (Enterprise only). -* **Email address** → `EMAIL`. Matches before the `@` ; only avoids mismatches from different email domains for the same person. -* **Date of birth / registration date** → `EXACT`. Dates should not have fuzzy tolerance. -* **Postal / ZIP code** → `PINCODE`. Handles common format variants. -* **Street address (full address line)** → `FUZZY` or `ONLY_ALPHABETS_FUZZY` combined with NUMERIC for the street number as a separate field. -* **Street number / apartment number** → `NUMERIC`. Extracts and compares the number portion only. -* **Internal record ID (not used for matching)** → `DONT_USE`. Appears in output but excluded from comparison. -* **Any field frequently null across source systems** → Add `NULL_OR_BLANK` alongside the main match type. -* **Product descriptions / notes** → TEXT. `Word` overlap comparison for longer free-text fields.
From bb13795ed6b6b325ce3b7e1bacf8944b08cf4e8c Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 12:23:35 +0000 Subject: [PATCH 083/144] GITBOOK-358: No subject --- docs/SUMMARY.md | 1 + .../how-zingg-learns/field-definition.md | 13 +++++++++++++ .../how-zingg-learns/match-types/README.md | 8 -------- 3 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 docs/zingg-concepts/how-zingg-learns/field-definition.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index c2fc9f84b..4c3de96d2 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -15,6 +15,7 @@ * [Zingg Models](zingg-concepts/how-zingg-learns/zingg-models/README.md) * [Blocking Model](zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) * [Similarity Model](zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md) + * [Field Definition](zingg-concepts/how-zingg-learns/field-definition.md) * [Match Types](zingg-concepts/how-zingg-learns/match-types/README.md) * [FUZZY Match](zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md) * [FUZZY\_OPTIMISED Match](zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md) diff --git a/docs/zingg-concepts/how-zingg-learns/field-definition.md b/docs/zingg-concepts/how-zingg-learns/field-definition.md new file mode 100644 index 000000000..514234233 --- /dev/null +++ b/docs/zingg-concepts/how-zingg-learns/field-definition.md @@ -0,0 +1,13 @@ +--- +description: Choosing what to match +--- + +# Field Definition + +Field Definition is Zingg is the configuration object that tells Zingg which fields to use for matching and how to compare them. + +Each field definition has four attributes: `fieldName` (the column name), `fields` (same as `fieldName` for now), `dataType` (string, integer, double, etc.), and `matchType` (the similarity functions to apply). + +{% hint style="success" icon="right-long" %} +**Read more:** [Configure Zingg](../../running-zingg/configure-zingg.md) +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/README.md b/docs/zingg-concepts/how-zingg-learns/match-types/README.md index e98f45d99..4f29506c2 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/README.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/README.md @@ -15,14 +15,6 @@ Multiple match types can be assigned to a single field, separated by commas.
Match TypeWhat it doesData typesBest forEditionExample
FUZZYBroad matching tolerant to typos,
abbreviations and variations. Uses fuzzy string similarity features.
string, integer, long, double, dateNames, addresses, free-text fieldsAll"Jon Smith", "Jonathan Smith", and
"J. Smith" all match the same record.
FUZZY_OPTIMISEDSame accuracy as FUZZY, approximately 2X faster. Lower CPU and memory usage on large datasets. Use this in production when FUZZY accuracy is needed at scalestring, integer, long, double, dateSame as FUZZY - use when dataset
is large and performance matters
Enterprise onlySame matching behaviour as FUZZY. Processes a 10M-record dataset in approximately half the time.
EXACTProvides an exact-match signal to the classifier. A strong indicator when two values align perfectly. Records may still match based on other fields and how training pairs were labeled.string, integer, long, date, booleanCountry codes, boolean flags,
categorical fields where no
variation is expected
All"US" and "US"—strong match signal. "US" and ""us"—no exact signal, but the record pair may still match if other fields score highly.
EMAILMatches only the local part before
the @ - ignores the domain entirely.
stringEmail address fieldsAll"john.smith@gmail.com" and
"john.smith@company.com" — match.
"john.smith@gmail.com" and
"jsmith@gmail.com" — no match.
EMAIL_OPTIMISEDSame as EMAIL but significantly faster
evaluation on large datasets.
stringEmail fields at production scaleEnterprise onlySame behavior as EMAIL.
Use when comparing millions of
email addresses.
PINCODEMatches postal and pin codes across
common format variants (e.g. xxxxx
and xxxxx-xxxx).
stringPostal/ZIP code fieldsAll"94102" and "94102-1234" — match.
"94102" and "94103" — no match.
NUMERICExtracts numbers from strings and
compares how many are the same
across both strings.
stringStreet numbers, apartment numbers,
building numbers
All"42B Main St" and "42 Main Street" —
the number 42 is extracted and matched
from both. Alphabetic variation ignored.
NUMERIC_WITH_UNITSExtracts product codes or numbers
with units (e.g. 16gb, 500ml) and
compares how many are the same.
stringProduct specification fields,
size field
All16GB" and "16 GB DDR4" — the 16 and "GB" tokens match. "16gb" and "32gb" — the units match but the numbers do not.
TEXTCompares overlapping words between two strings. Good for longer descriptive text without many typos.stringProduct descriptions, notes,
long free-text fields
All"Enterprise data management platform"
and "data management platform for
enterprise" — high overlap, likely match.
"Enterprise software" and
"consumer hardware" — low overlap,
likely no match.
ONLY_ALPHABETS_EXACTIgnores all numbers, then requires an exact letter match.stringBuilding names where unit numbers
should be ignored
All"Tower A, Flat 12" and "Tower A, Flat 7"
— "Tower A" matches exactly after
numbers are stripped.
"Tower A" and "Tower B" — no match.
ONLY_ALPHABETS_FUZZYIgnores all numbers, then applies fuzzy matching to alphabetic characters onlystringAddress fields where street names
need fuzzy matching and numbers
are handled separately via NUMERIC
All"St. James Rd" and "Saint James Road" —
alphabetic characters compared fuzzily.
Combine with NUMERIC for the
street number.
ONLY_ALPHABETS_FUZZY_OPTIMISEDSame as ONLY_ALPHABETS_FUZZY but optimised for production scale.stringSame as ONLY_ALPHABETS_FUZZY -use when dataset is largeEnterprise onlySame matching behaviour.
Use for large address datasets.
NULL_OR_BLANKBy default Zingg treats nulls as
matches. Add this alongside another
match type to build an explicit feature for null/blank values so the model can learn their effect.
string, integer, long, date, booleanAny field frequently null or blank
across source systems -combine
with FUZZY or EXACT
AllField A: null, Field B: "John" — with NULL_OR_BLANK added, these are NOT treated as matching. Without it, null matches anything.
DONT_USEAppears in output but no computation
is done. Not shown in the labeller
when showConcise is true.
anyRecord IDs, internal keys,
any field needed in output
but not in matching
AllA customer_id field that must appear
in output for traceability but should
not influence whether two records
are the same entity.
MAPPING_(FILENAME)Maps input field values to canonical
values using a user-supplied lookup file. Handles nicknames, abbreviations,
company name variants, and categorical
normalisation. Matching is tolerant
to common variations defined in the mapping file.
stringName fields (nicknames), company
fields, categorical fields with
different representations across
systems
Enterprise only"Jon", "Jonathan", "Johnny" all map
to canonical "John" via nicknames.json.
"M", "Male", "1" all map to "M"
via gender.json. Matched before
similarity scoring runs.
-
- -Which match type should I use for each field type? - - - -
- {% hint style="success" icon="right-long" %} **Read More:** From cb65ee8c4a54b3082ff128655787c21939acc1d8 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 12:25:00 +0000 Subject: [PATCH 084/144] GITBOOK-359: No subject --- .../zingg-concepts/platform-infrastructure-vs-data-platform.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md b/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md index 3e4151a10..83fc31b97 100644 --- a/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md +++ b/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md @@ -4,10 +4,11 @@ description: Where Zingg runs, which data is resolves # Platform Infrastructure vs Data Platform -Two things that are easy to conflate but mean different things in Zingg's context. Platform infrastructure is where computation happens, such as the Databricks/Fabric/EMR/Glue/Dataproc Spark service or Snowflake compute that processes your data. A data platform is where your data lives - Snowflake tables, Databricks Lakehouse and Unity Catalog, BigQuery, Redshift, or a file store like S3. +Two things that are easy to conflate but mean different things in Zingg's context. Platform infrastructure is where computation happens, such as the Databricks/Fabric/EMR/Glue/Dataproc Spark service or Snowflake compute that processes your data. A data platform is where your data lives - Snowflake tables, Databricks Lakehouse and Unity Catalog, OneLake, BigQuery, Redshift, or a file store like S3. These are independent: you can have your data in Snowflake but run Zingg's computation on Spark. Zingg connects to the data platform and runs computations on whichever engine you configure. {% hint style="success" icon="right-long" %} **Read more:** [Connect data](../connect-your-data/pipes-and-data-connections.md) | [Install Zingg](../running-zingg/install-zingg.md) {% endhint %} + From e05b3585ff939bb0d49828b8a21d4854507e1971 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 12:26:22 +0000 Subject: [PATCH 085/144] GITBOOK-360: No subject --- .../match-types/fuzzy-match.md | 51 +------------------ 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md index cc9bc48fe..833e20ebe 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md @@ -59,6 +59,7 @@ Use `ONLY_ALPHABETS_FUZZY` for the street name component (ignores numbers, appli Never use `FUZZY` on fields that are reliable unique identifiers. `FUZZY` tolerance on an SSN field means "123-45-6789" and "123-45-6780" could score above the match threshold, that is a false positive you cannot afford in a compliance context. Use `EXACT` for trusted identifiers. +
@@ -83,59 +84,9 @@ Use `PINCODE` not `FUZZY` for postal codes. `PINCODE` is built to handle the spe `FUZZY` is computationally heavier than `FUZZY_OPTIMISED`. For production runs on large datasets where you want the same matching quality with faster results, use `FUZZY_OPTIMISED` instead. -
-### Configuring `FUZZY` - -{% tabs %} -{% tab title="Python" %} -### **Community** - -```python -from zingg.client import* - -fname = FieldDefinition("fname", "string", MatchType.FUZZY) -lname = FieldDefinition("lname", "string", MatchType.FUZZY) -``` - -### **Enterprise** - -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zingg.client import * - -fname = EFieldDefinition("fname", "string", MatchType.FUZZY) -lname = EFieldDefinition("lname", "string", MatchType.FUZZY) -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). -{% endhint %} -```json -{ - "fieldDefinition" : [ - { - "fieldName" : "fname", - "matchType" : "fuzzy", - "fields" : "fname", - "dataType" : "string" - }, - { - "fieldName" : "lname", - "matchType" : "fuzzy", - "fields" : "lname", - "dataType" : "string" - } - ] -} -``` - -{% endtab %} -{% endtabs %} {% hint style="success" icon="right-long" %} **Related match types:** From ebefa70fd10cc5f49d70078530c980537c81bb6f Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 12:32:04 +0000 Subject: [PATCH 086/144] GITBOOK-361: No subject --- .../match-types/fuzzy_optimised-match.md | 69 +------------------ 1 file changed, 2 insertions(+), 67 deletions(-) diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md index c98ffc5de..4ac3ef88d 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md @@ -1,7 +1,7 @@ --- description: >- - Same matching quality as FUZZY with significantly lower time. - The production-scale choice when FUZZY accuracy is needed on large datasets. + Same matching quality as FUZZY with significantly lower time. The + production-scale choice when FUZZY accuracy is needed on large datasets. tags: - ent - tag: enterprise-only @@ -20,77 +20,12 @@ tags: The scoring pattern is exactly the same as `FUZZY` - their results are not affected. The only difference is computation time: `FUZZY_OPTIMISED` runs in roughly half the time `FUZZY` takes on the same data. -### What `FUZZY_OPTIMISED` matches and what it does not - -
Value AValue BMatch?
Jonathan SmithJon SmithYes - same as FUZZY
J. SmithJohn SmithYes - same as FUZZY
JonathonJonathanYes
JohnsonSmithNo - too different
IBM CorpIBM CorporationYes - long shared prefix scores high, same as with FUZZY
[null]John SmithDepends - add NULL_OR_BLANK to control null behaviour
- For harder abbreviation cases like "IBM" vs "International Business Machines", `FUZZY_OPTIMISED` needs the same help `FUZZY` does - the strings are too dissimilar on their own, so combine with `MAPPING_(company_names)` to map the representative form explicitly. -### When to use `FUZZY_OPTIMISED` - -
- -Production runs on large datasets - -Any field where you would use `FUZZY` is a candidate for `FUZZY_OPTIMISED` in production. The matching output is the same. The resource cost is lower. - -Switch from `FUZZY` to `FUZZY_OPTIMISED` when you move from evaluation to production, or when a `FUZZY` run is taking longer or consuming more memory than your infrastructure allows. - -
- -
- -Name and address fields at scale - -Customer names, company names, street addresses, and city fields on datasets of 1M records or more. The performance improvement is most visible on string fields with high variation, exactly the fields where `FUZZY` is typically applied. - -
- -### **When not to use `FUZZY_OPTIMISED`** - -
- -When you need exact matching - -`FUZZY_OPTIMISED` carries the same tolerance for variation as `FUZZY` - it is not a substitute for exact comparison. On trusted identifiers, dates, and postal codes, that tolerance is a false positive risk you cannot afford. Use `EXACT` for identifiers and dates, `PINCODE` for postal codes. - -
- {% hint style="info" icon="right-long" %} `FUZZY_OPTIMISED` is **Enterprise** only. Community users use `FUZZY`. {% endhint %} -### Configuring `FUZZY_OPTIMISED` - -{% tabs %} -{% tab title="Python" %} -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zinggEC.enterprise.common.EMatchTypes import * - -fname = EFieldDefinition("fname", "string", EMatchType.FUZZY_OPTIMISED) -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block below uses Enterprise-only match type `FUZZY_OPTIMISED`. -{% endhint %} - -```json -{ - "fieldDefinition" : [ { - "fieldName" : "fname", - "matchType" : "fuzzy_optimised", - "fields" : "fname", - "dataType" : "string" - } ] -} -``` - -{% endtab %} -{% endtabs %} - {% hint style="success" icon="right-long" %} **Related Match types:** From 2b8ea7aee294e537180429d75aac1fcfb29cf803 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 12:36:07 +0000 Subject: [PATCH 087/144] GITBOOK-362: No subject --- .../match-types/dont_use-match.md | 38 -------------- .../match-types/email-match.md | 47 ++--------------- .../match-types/email_optimised-match.md | 32 ------------ .../match-types/exact-match.md | 50 ------------------- .../match-types/mapping_-filename-match.md | 33 +----------- .../match-types/null_or_blank-match.md | 46 ++--------------- .../match-types/numeric-match.md | 41 +-------------- .../match-types/numeric_with_units-match.md | 49 ++---------------- .../match-types/only_alphabets_exact-match.md | 39 --------------- .../match-types/only_alphabets_fuzzy-match.md | 43 +--------------- .../only_alphabets_fuzzy_optimised-match.md | 33 +----------- .../match-types/pincode-match.md | 43 +--------------- .../match-types/text-match.md | 41 +-------------- 13 files changed, 19 insertions(+), 516 deletions(-) diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/dont_use-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/dont_use-match.md index 69792b86d..5bf67b076 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/dont_use-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/dont_use-match.md @@ -70,44 +70,6 @@ For example, a `region_code` field cheaply splits records into non-overlapping c
-{% tabs %} -{% tab title="Python" %} -### **Community** - -```python -from zingg.client import * -rec_id = FieldDefinition("rec_id", "string", MatchType.DONT_USE) -``` - -### **Enterprise** - -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zingg.client import * - -rec_id = EFieldDefinition("rec_id", "string", MatchType.DONT_USE) -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). -{% endhint %} - -```json -{ - "fieldDefinition" : [ { - "fieldName" : "rec_id", - "matchType" : "dont_use", - "fields" : "rec_id", - "dataType" : "string" - } ] -} -``` - -{% endtab %} -{% endtabs %} - {% hint style="success" icon="right-long" %} **Related types**: diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/email-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/email-match.md index 05a6e5411..687fe8d37 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/email-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/email-match.md @@ -1,8 +1,8 @@ --- description: >- - Fuzzy-matches only the local part of an email address before the @ symbol - and ignores the domain. Built for datasets where the same person appears - with different email domains. + Fuzzy-matches only the local part of an email address before the @ symbol and + ignores the domain. Built for datasets where the same person appears with + different email domains. --- # EMAIL Match @@ -59,47 +59,6 @@ Use `EXACT` on the full email field when the domain matters.
-### Configuring `EMAIL` - -{% tabs %} -{% tab title="Python" %} -### **Community** - -```python -from zingg.client import* - -email = FieldDefinition("email", "string", MatchType.EMAIL) -``` - -### **Enterprise** - -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zingg.client import * - -email = EFieldDefinition("email", "string", MatchType.EMAIL) -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). -{% endhint %} - -```json -{ - "fieldDefinition" : [ { - "fieldName" : "email", - "matchType" : "email", - "fields" : "email", - "dataType" : "string" - } ] -} -``` - -{% endtab %} -{% endtabs %} - {% hint style="success" icon="right-long" %} **Related types**: diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md index 49f004254..bd6734285 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md @@ -45,38 +45,6 @@ performance matters. Customer datasets, patient registries, voter files—any do - -### Configuring `EMAIL_OPTIMISED` - -{% tabs %} -{% tab title="Python" %} -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zinggEC.enterprise.common.EMatchTypes import * - -email = EFieldDefinition("email", "string", EMatchType.EMAIL_OPTIMISED) -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block below uses Enterprise-only match type `EMAIL_OPTIMISED`. -{% endhint %} - -```json -{ - "fieldDefinition" : [ { - "fieldName" : "email", - "matchType" : "email_optimised", - "fields" : "email", - "dataType" : "string" - } ] -} -``` - -{% endtab %} -{% endtabs %} - {% hint style="success" icon="right-long" %} **Related types**: diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md index 654b8924a..5c7162eb9 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md @@ -76,56 +76,6 @@ Never use `EXACT` on name fields. Even internally consistent data has legitimate -### Configuring `EXACT` - -{% tabs %} -{% tab title="Python" %} -### **Community** - -```python -from zingg.client import * - -ssn = FieldDefinition("ssn", "string", MatchType.EXACT) -dob = FieldDefinition("dob", "string", MatchType.EXACT) -``` - -### **Enterprise** - -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zingg.client import * - -ssn = EFieldDefinition("ssn", "string", MatchType.EXACT) -dob = EFieldDefinition("dob", "string", MatchType.EXACT) -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). -{% endhint %} - -```json -{ - "fieldDefinition" : [ - { - "fieldName" : "ssn", - "matchType" : "exact", - "fields" : "ssn", - "dataType" : "string" - }, - { - "fieldName" : "dob", - "matchType" : "exact", - "fields" : "dob", - "dataType" : "string" - } - ] -} -``` -{% endtab %} -{% endtabs %} - {% hint style="success" icon="right-long" %} **Related types:** diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md index e30713423..444aadc2c 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md @@ -47,7 +47,7 @@ The mapping file lists groups of equivalent values. `MAPPING` is typically combi ] ``` -Each inner array is one equivalence group. The longest value in a group becomes its representative form - so "Jon" normalizes to "Jonathan", and "Bob"/"Rob"/"Bobby" all normalize to "Robert". This is the exact dictionary used to produce the table above. "Jhon" and "John" aren't listed anywhere, so `MAPPING` leaves them untouched, and only `FUZZY` can catch them. +The mapping file is a JSON array where each element is an array of equivalent values. Each inner array is one equivalence group. The longest value in a group becomes its representative form - so "Jon" normalizes to "Jonathan", and "Bob"/"Rob"/"Bobby" all normalize to "Robert". This is the exact dictionary used to produce the table above. "Jhon" and "John" aren't listed anywhere, so `MAPPING` leaves them untouched, and only `FUZZY` can catch them. ### When to use `MAPPING` @@ -109,34 +109,3 @@ Dates should use `EXACT`. Different formats of the same date (`2024-01-15` vs `0 -### Configuring `MAPPING` - -The mapping file is a JSON array where each element is an array of equivalent values. - -{% tabs %} -{% tab title="Python" %} -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition - -fname = EFieldDefinition("fname", "string", "MAPPING_nicknames,FUZZY") -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block below uses Enterprise-only match type `mapping_`. Replace `` with the name of your mapping file without the `.json` extension. -{% endhint %} - -```json -{ - "fieldDefinition" : [ { - "fieldName" : "fname", - "matchType" : "mapping_nicknames,fuzzy", - "fields" : "fname", - "dataType" : "string" - } ] -} -``` - -{% endtab %} -{% endtabs %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md index 9debf9ae7..0217d541d 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md @@ -1,6 +1,5 @@ --- -description: >- - Changes how Zingg handles null and blank values in a field. +description: Changes how Zingg handles null and blank values in a field. --- # NULL\_OR\_BLANK Match @@ -17,7 +16,7 @@ Use `NULL_OR_BLANK` combined with another match type, in the `matchType` string ### What **`NULL_OR_BLANK`** matches and what it does not -
Value AValue BNULL_OR_BLANK featureNotes
nullJohn Smith0.0One side null
[empty string]John Smith0.0Empty string treated exactly like null
nullnull0.0No distinction between one-sided and both-sided nulls
null[empty string]0.0Null and blank are interchangeable
John SmithJohn Smith1.0Both values present
+
Value AValue BNULL_OR_BLANK featureNotes
nullJohn Smith0.0One side null
[empty string]John Smith0.0Empty string treated exactly like null
nullnull0.0No distinction between one-sided and both-sided nulls
null[empty string]0.0Null and blank are interchangeable
John SmithJohn Smith1.0Both values present
There is no distinction between "both null" and "one null, one populated" - all four null/blank rows above score identically. @@ -63,45 +62,6 @@ If a field is universally null across all records with no non-null values to con -{% tabs %} -{% tab title="Python" %} -### **Community** - -```python -from zingg.client import * - -company = FieldDefinition("company", "string", "FUZZY,NULL_OR_BLANK") -``` - -### **Enterprise** - -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zingg.client import * - -company = EFieldDefinition("company", "string", "FUZZY,NULL_OR_BLANK") -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). -{% endhint %} - -```json -{ - "fieldDefinition" : [ { - "fieldName" : "company", - "matchType" : "fuzzy,null_or_blank", - "fields" : "company", - "dataType" : "string" - } ] -} -``` - -{% endtab %} -{% endtabs %} - {% hint style="success" icon="right-long" %} `NULL_OR_BLANK` common combinations: @@ -110,5 +70,5 @@ The JSON `fieldDefinition` block is identical for Community and Enterprise. Only * `PINCODE`, `NULL_OR_BLANK` - postal codes missing in some systems * `DONT_USE` - if the field should be excluded from matching entirely -**Read more**: [Match Types](README.md) +**Read more**: [Match Types](./) {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md index 6791e0364..6a4b7f403 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md @@ -59,50 +59,11 @@ For "16gb", "500ml", and similar specifications where the unit matters as much a -{% tabs %} -{% tab title="Python" %} -### **Community** - -```python -from zingg.client import * - -address = FieldDefinition("address", "string", MatchType.NUMERIC) -``` - -### **Enterprise** - -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zingg.client import * - -address = EFieldDefinition("address", "string", MatchType.NUMERIC) -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). -{% endhint %} - -```json -{ - "fieldDefinition" : [ { - "fieldName" : "address", - "matchType" : "numeric", - "fields" : "address", - "dataType" : "string" - } ] -} -``` - -{% endtab %} -{% endtabs %} - {% hint style="success" icon="right-long" %} **Related matches:** * `NUMERIC_WITH_UNITS` - for product codes that include units (16gb, 500ml) * `FUZZY` - if you want a single match type covering the whole string -**Read more**: [Match Types](README.md) +**Read more**: [Match Types](./) {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md index 501c8ab87..484624964 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md @@ -1,7 +1,7 @@ --- description: >- - Extracts product codes or numbers with units and compares how many are the same across both values. Built for product - specification fields. + Extracts product codes or numbers with units and compares how many are the + same across both values. Built for product specification fields. --- # NUMERIC\_WITH\_UNITS Match @@ -10,9 +10,9 @@ description: >- `NUMERIC_WITH_UNITS` extracts contiguous alphanumeric tokens that contain a digit - the number and any unit letters glued directly to it, like "16gb" or "2.4GHz" - and compares how many of those tokens are shared across two records. -**The unit only glues to the number when there's no space between them.** "16gb" extracts as the single token `16gb`, but "16 GB" (with a space) extracts as just `16` - the unit is dropped, not glued. So a number and its unit written with a space in one record and without a space in the other extract to *different* tokens and won't match: `{16gb}` vs `{16}` share nothing. There is no unit-conversion logic either - "500ml" and "0.5L" extract to `{500ml}` and `{0.5L}`, which share nothing, regardless of them being the same physical quantity. +**The unit only glues to the number when there's no space between them.** "16gb" extracts as the single token `16gb`, but "16 GB" (with a space) extracts as just `16` - the unit is dropped, not glued. So a number and its unit written with a space in one record and without a space in the other extract to _different_ tokens and won't match: `{16gb}` vs `{16}` share nothing. There is no unit-conversion logic either - "500ml" and "0.5L" extract to `{500ml}` and `{0.5L}`, which share nothing, regardless of them being the same physical quantity. -**Null handling is a third, distinct behavior**, different from both `NUMERIC` and every other match type: if *both* sides extract zero tokens (both null/blank, or both have no digit anywhere), it scores a match (1.0). But if only *one* side extracts zero tokens - including one side being null while the other has real tokens - it scores a non-match (0.0), the same as `NUMERIC`. +**Null handling is a third, distinct behavior**, different from both `NUMERIC` and every other match type: if _both_ sides extract zero tokens (both null/blank, or both have no digit anywhere), it scores a match (1.0). But if only _one_ side extracts zero tokens - including one side being null while the other has real tokens - it scores a non-match (0.0), the same as `NUMERIC`. ### What `NUMERIC_WITH_UNITS` matches and what it does not @@ -48,50 +48,11 @@ Use `NUMERIC` for address fields. "42 Main Street" does not have units. `NUMERIC -{% tabs %} -{% tab title="Python" %} -### **Community** - -```python -from zingg.client import * - -weight = FieldDefinition("weight", "string", MatchType.NUMERIC_WITH_UNITS) -``` - -### **Enterprise** - -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zingg.client import * - -weight = EFieldDefinition("weight", "string", MatchType.NUMERIC_WITH_UNITS) -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). -{% endhint %} - -```json -{ - "fieldDefinition" : [ { - "fieldName" : "weight", - "matchType" : "numeric_with_units", - "fields" : "weight", - "dataType" : "string" - } ] -} -``` - -{% endtab %} -{% endtabs %} - {% hint style="success" icon="right-long" %} **Related types**: * `NUMERIC` - for numbers without unit * `TEXT` - for the descriptive parts of product fields -**Read more**: [Match types](README.md) +**Read more**: [Match types](./) {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_exact-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_exact-match.md index 71c4f53d5..ff8c2a5ef 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_exact-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_exact-match.md @@ -71,45 +71,6 @@ Use `ONLY_ALPHABETS_FUZZY` instead when the alphabetic component also has spelli -{% tabs %} -{% tab title="Python" %} -### **Community** - -```python -from zingg.client import * - -street = FieldDefinition("street", "string", MatchType.ONLY_ALPHABETS_EXACT) -``` - -### **Enterprise** - -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zinggEC.enterprise.common.EMatchTypes import * - -street = EFieldDefinition("street", "string", MatchType.ONLY_ALPHABETS_EXACT) -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). -{% endhint %} - -```json -{ - "fieldDefinition" : [ { - "fieldName" : "street", - "matchType" : "only_alphabets_exact", - "fields" : "street", - "dataType" : "string" - } ] -} -``` - -{% endtab %} -{% endtabs %} - {% hint style="success" icon="right-long" %} **Related types**: diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy-match.md index 918ab8bbc..a101d6786 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy-match.md @@ -11,7 +11,6 @@ description: >- `ONLY_ALPHABETS_FUZZY` removes all numeric characters from both field values and then applies fuzzy string similarity to the remaining alphabetic characters. Two values match based on how similar their alphabetic portions are with tolerance for typos, abbreviations, and spelling variants. - ### What **`ONLY_ALPHABETS_FUZZY`** matches and what it does not
Value AValue BMatch?
42 Main Street44 Main StreetYes - numbers stripped, "Main Street" is identical
42 Main St44 Main StreetYes - "Main St" vs "Main Street" scores a high fuzzy similarity
42 Main St42 Oak StNo - "Main St" vs "Oak St" too different alphabetically
[null]Main StreetYes - a null/blank value on either side is an automatic match; add NULL_OR_BLANK if you want nulls excluded
@@ -94,51 +93,13 @@ If your address data is structured enough that `FUZZY` on the full string gives -{% tabs %} -{% tab title="Python" %} -### **Community** - -```python -from zingg.client import * -street = FieldDefinition("street", "string", MatchType.ONLY_ALPHABETS_FUZZY) -``` - -### **Enterprise** - -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zingg.client import * - -street = EFieldDefinition("street", "string", MatchType.ONLY_ALPHABETS_FUZZY) -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). -{% endhint %} - -```json -{ - "fieldDefinition" : [ { - "fieldName" : "street", - "matchType" : "only_alphabets_fuzzy", - "fields" : "street", - "dataType" : "string" - } ] -} -``` - -{% endtab %} -{% endtabs %} - {% hint style="success" icon="right-long" %} **Related types**: * `NUMERIC` - use this for numeric fields * `ONLY_ALPHABETS_EXACT` - use when the name must match exactly (no abbreviation tolerance) -* `ONLY_ALPHABETS_FUZZY_OPTIMISED` - same match type, faster at scale +* `ONLY_ALPHABETS_FUZZY_OPTIMISED` - same match type, faster at scale * `FUZZY` - simpler alternative for the full string -**Read more**: [Match Types](README.md) +**Read more**: [Match Types](./) {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md index fb0edf562..84c82ed06 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md @@ -48,37 +48,6 @@ Any field where you would use `ONLY_ALPHABETS_FUZZY` - street addresses, product `ONLY_ALPHABETS_FUZZY_OPTIMISED` is **Enterprise** only. Use `ONLY_ALPHABETS_FUZZY` in Community. {% endhint %} -### Configuring `ONLY_ALPHABETS_FUZZY_OPTIMISED` - -{% tabs %} -{% tab title="Python" %} -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zinggEC.enterprise.common.EMatchTypes import * - -street = EFieldDefinition("street", "string", EMatchType.ONLY_ALPHABETS_FUZZY_OPTIMISED) -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block below uses Enterprise-only match type `ONLY_ALPHABETS_FUZZY_OPTIMISED`. The CLI command is identical to other phases. -{% endhint %} - -```json -{ - "fieldDefinition" : [ { - "fieldName" : "street", - "matchType" : "only_alphabets_fuzzy_optimised", - "fields" : "street", - "dataType" : "string" - } ] -} -``` - -{% endtab %} -{% endtabs %} - {% hint style="success" icon="right-long" %} **Related types**: @@ -86,5 +55,5 @@ The JSON `fieldDefinition` block below uses Enterprise-only match type `ONLY_ALP * `FUZZY_OPTIMISED` - same optimisation pattern for name/text fields * `NUMERIC` - for numeric fields -**Read more**: [Match Types](README.md) +**Read more**: [Match Types](./) {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md index 3ddc510f9..6428b466e 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md @@ -13,7 +13,7 @@ description: >- A null or blank value on either side is an automatic match. -**This is not a general international postal-code normalizer.** The hyphen-truncation logic assumes the part after the hyphen is always a discardable suffix. That's true for US ZIP+4, but many countries use a hyphen as a meaningful separator where *both* halves matter - for example, Polish postal codes ("00-950" vs "00-123") or Portuguese ones. On those formats, `PINCODE` truncates both sides down to "00" and reports a match, silently collapsing two genuinely different postal codes. See the table and "When not to use" below. +**This is not a general international postal-code normalizer.** The hyphen-truncation logic assumes the part after the hyphen is always a discardable suffix. That's true for US ZIP+4, but many countries use a hyphen as a meaningful separator where _both_ halves matter - for example, Polish postal codes ("00-950" vs "00-123") or Portuguese ones. On those formats, `PINCODE` truncates both sides down to "00" and reports a match, silently collapsing two genuinely different postal codes. See the table and "When not to use" below. ### What `PINCODE` matches and what it does not @@ -51,45 +51,6 @@ If your postal code format uses a hyphen where both sides carry meaning, use `EX -{% tabs %} -{% tab title="Python" %} -### **Community** - -```python -from zingg.client import * - -pincode = FieldDefinition("pincode", "string", MatchType.PINCODE) -``` - -### **Enterprise** - -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zingg.client import * - -pincode = EFieldDefinition("pincode", "string", MatchType.PINCODE) -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). -{% endhint %} - -```json -{ - "fieldDefinition" : [ { - "fieldName" : "pincode", - "matchType" : "pincode", - "fields" : "pincode", - "dataType" : "string" - } ] -} -``` - -{% endtab %} -{% endtabs %} - {% hint style="success" icon="right-long" %} **Related types:** @@ -97,5 +58,5 @@ The JSON `fieldDefinition` block is identical for Community and Enterprise. Only * `NUMERIC` - for other numeric identifier fields * `NULL_OR_BLANK` - combine when postal codes are often missing -**Read more:** [Match Types](README.md) +**Read more:** [Match Types](./) {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/text-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/text-match.md index 688fe0d50..51533e501 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/text-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/text-match.md @@ -60,50 +60,11 @@ If your free-text fields have typos and spelling errors, `FUZZY` handles those b -{% tabs %} -{% tab title="Python" %} -### **Community** - -```python -from zingg.client import * - -description = FieldDefinition("description", "string", MatchType.TEXT) -``` - -### **Enterprise** - -```python -from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition -from zingg.client import * - -description = EFieldDefinition("description", "string", MatchType.TEXT) -``` -{% endtab %} - -{% tab title="JSON" %} -{% hint style="info" icon="right-long" %} -The JSON `fieldDefinition` block is identical for Community and Enterprise. Only the Python class differs between editions — `FieldDefinition` (Community) vs `EFieldDefinition` (Enterprise). -{% endhint %} - -```json -{ - "fieldDefinition" : [ { - "fieldName" : "description", - "matchType" : "text", - "fields" : "description", - "dataType" : "string" - } ] -} -``` - -{% endtab %} -{% endtabs %} - {% hint style="info" icon="right-long" %} **Related types:** * `FUZZY` - better for short fields and when typos are present * `NUMERIC_WITH_UNITS` - Use for product description fields -**Read more**: [Match Types](README.md) +**Read more**: [Match Types](./) {% endhint %} From a61f4511f419ceffbbc99379a47f78756acf8b27 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 12:40:39 +0000 Subject: [PATCH 088/144] GITBOOK-363: No subject --- .../zingg-entity-resolution-platform/incremental-flow.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md b/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md index 9cc60cd3f..8d2f034c0 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md @@ -1,9 +1,12 @@ --- -description: Continuously updated identity graph +description: When it is not enough to match once --- # Incremental Flow +Data in enteprise systems gets regularly updated. The business expands, and new customer records come in. Entities change - people move, get new phones, register new businesses. Some systems cease and records get deleted. Matching once is powerful, but it ceases to be enough in such cases. A full rematch is costly, and loses state. + +\ The process of updating the identity graph with new, changed, or deleted records without re-running the full match across the entire dataset is known as incremental flow in Zingg. Zingg incorporates incoming records into existing clusters, handles cluster merges and unmerges automatically, generates new Zingg IDs for records that do not match any existing cluster, and preserves human-approved decisions so they are not overridden. Many tools claim incremental capability but cannot maintain match quality and stable IDs simultaneously. This feature is a key Zingg Enterprise differentiator. _Enterprise only_. From 1c721fa3ec80dcfbcc424fa482d713a374f964ae Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 12:42:36 +0000 Subject: [PATCH 089/144] GITBOOK-364: No subject --- docs/SUMMARY.md | 1 - docs/running-zingg/verify-blocking.md | 8 +-- docs/tuning/blocking-strategy.md | 10 +-- docs/tuning/custom-blocking-and-similarity.md | 72 ------------------- docs/tuning/improve-accuracy/README.md | 7 +- 5 files changed, 12 insertions(+), 86 deletions(-) delete mode 100644 docs/tuning/custom-blocking-and-similarity.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 4c3de96d2..80f84264a 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -97,7 +97,6 @@ * [Configure Field Standardization](tuning/configure-field-standardization.md) * [Improve Accuracy](tuning/improve-accuracy/README.md) * [Remove Stopwords (Optional)](tuning/improve-accuracy/remove-stopwords-optional.md) -* [Custom Blocking and Similarity](tuning/custom-blocking-and-similarity.md) * [Blocking Strategies: DEFAULT vs WIDER](tuning/blocking-strategy.md) ## Interpreting Results diff --git a/docs/running-zingg/verify-blocking.md b/docs/running-zingg/verify-blocking.md index 6a41b5540..31c6d6e1f 100644 --- a/docs/running-zingg/verify-blocking.md +++ b/docs/running-zingg/verify-blocking.md @@ -94,11 +94,9 @@ influence matching. * [Label Training Pairs](label-training-pairs.md) - how to add more training data * [Configure Zingg](configure-zingg.md) - changing field match types * [Blocking Strategies: DEFAULT vs WIDER](../tuning/blocking-strategy.md) - reorder candidate fields in the blocking tree (Enterprise only) -* [Custom Blocking and Similarity](../tuning/custom-blocking-and-similarity.md) Functions +* [Custom Blocking and Similarity](/broken/pages/DWdyf7az3MmhJaVca3k7) Functions {% endhint %} - - {% hint style="success" icon="right-long" %} Run `verifyBlocking` in two situations: @@ -176,10 +174,10 @@ If `verifyBlocking` shows that many known matching pairs are not being blocked t 3. **Try the `WIDER` blocking strategy** (Enterprise only) if large blocks suggest the tree is over-relying on one or two fields → [Blocking Strategies: DEFAULT vs WIDER](../tuning/blocking-strategy.md) {% hint style="success" icon="right-long" %} -**Consider custom blocking functions** for specialised data patterns → [Custom Blocking and Similarity](../tuning/custom-blocking-and-similarity.md) +**Consider custom blocking functions** for specialised data patterns → [Custom Blocking and Similarity](/broken/pages/DWdyf7az3MmhJaVca3k7) **Read more**: * Blocking model concept and how it fits in the pipeline → [Blocking Model](../zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) -* Custom blocking functions for advanced tuning → [Custom Blocking and Similarity](../tuning/custom-blocking-and-similarity.md) +* Custom blocking functions for advanced tuning → [Custom Blocking and Similarity](/broken/pages/DWdyf7az3MmhJaVca3k7) {% endhint %} diff --git a/docs/tuning/blocking-strategy.md b/docs/tuning/blocking-strategy.md index f941b5a5e..20e65bbf2 100644 --- a/docs/tuning/blocking-strategy.md +++ b/docs/tuning/blocking-strategy.md @@ -2,6 +2,8 @@ description: >- Choose between DEFAULT and WIDER blocking strategies to control how Zingg orders candidate fields when building the blocking tree. +tags: + - enterprise-only --- # Blocking Strategies: DEFAULT vs WIDER @@ -12,12 +14,12 @@ description: >- ## What each strategy does -Zingg builds a blocking tree greedily. At every node it asks: *of the eligible field/hash-function combinations, which one splits the group into smaller pieces?* That combination becomes the split at this node, and the process recurses into each resulting group. +Zingg builds a blocking tree greedily. At every node it asks: _of the eligible field/hash-function combinations, which one splits the group into smaller pieces?_ That combination becomes the split at this node, and the process recurses into each resulting group. -The **strategy** controls only the *order* candidate fields are offered at each node - never the comparison logic itself: +The **strategy** controls only the _order_ candidate fields are offered at each node - never the comparison logic itself: * **`DEFAULT`** - every node is offered your `fieldDefinition` list in the exact order you configured it. The field listed first is always tried first, everywhere in the tree. -* **`WIDER`** - each node looks at which field its *parent* node just used, and pushes that field to the back of the candidate list for this node, so the very next field in line gets first consideration instead. This repeats going down the tree, so the field that "won" one level isn't automatically favored again immediately below it. +* **`WIDER`** - each node looks at which field its _parent_ node just used, and pushes that field to the back of the candidate list for this node, so the very next field in line gets first consideration instead. This repeats going down the tree, so the field that "won" one level isn't automatically favored again immediately below it.
DEFAULT vs WIDER field ordering down the blocking tree

DEFAULT vs WIDER field ordering down the blocking tree

@@ -74,5 +76,5 @@ args.setBlockingModel("WIDER") * Set up field definitions and the arguments object this key belongs to → [Configure Zingg](../running-zingg/configure-zingg.md) * Check block-size distribution and coverage after changing strategy → [Verify Blocking](../running-zingg/verify-blocking.md) * Blocking model concept and how it fits in the pipeline → [Blocking Model](../zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) -* Define your own blocking functions for specialized data patterns → [Custom Blocking and Similarity](custom-blocking-and-similarity.md) +* Define your own blocking functions for specialized data patterns → [Custom Blocking and Similarity](/broken/pages/DWdyf7az3MmhJaVca3k7) {% endhint %} diff --git a/docs/tuning/custom-blocking-and-similarity.md b/docs/tuning/custom-blocking-and-similarity.md deleted file mode 100644 index 5f4441439..000000000 --- a/docs/tuning/custom-blocking-and-similarity.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -description: >- - Define your own blocking and similarity functions to replace or extend Zingg's - built-in approaches for advanced accuracy tuning. ---- - -# Custom Blocking and Similarity - -Zingg's built-in blocking and similarity functions handle most entity resolution use cases. For specialized data, such as company names with legal suffixes, phonetically similar names, or\ -domain-specific identifiers; you can define your own functions. - -Custom blocking functions control which records are compared. Custom similarity functions control how two field values are scored when they are compared. - -### Custom blocking functions - -Zingg evaluates custom blocking functions when building the blocking tree. The blocking tree works on the matched records you provided during labeling; at every node, Zingg selects the hash function and the field that produces the least elimination of your known matching pairs. - -**What makes a good blocking function:** - -A good blocking function never eliminates a matching pair entirely. It groups records that could be the same entity, even imperfectly; so the similarity model can evaluate them. A poor blocking function eliminates matching pairs from comparison entirely; no similarity scoring happens after that point. - -**Example: evaluating `first1char` on `firstname`** - -Take two labeled matching pairs: - -| 1 | A | john | j | -| - | - | ----- | - | -| 1 | B | johnh | j | -| 2 | A | mary | m | -| 2 | B | marry | m | - -Both pairs produce the same output from `first1char` - no elimination. This is a good function for `firstname`. - -**Contrast: `last1char` on `firstname`** - -| **1** | A | john | n | -| ----- | - | ----- | - | -| 1 | B | johnh | h | -| 1 | A | mary | y | -| 2 | B | marry | y | - -Pair 1 is eliminated (`n` ≠ `h`). `last1char` is not a good function for `firstname`. Zingg will therefore not choose it. - -So `first1char(firstname)` will be selected. It brings near-similar records together - clustering them to break the cartesian join. - -#### Registering custom blocking functions - -Business-specific blocking functions must be added to two places: - -1. **`HashFunctionRegistry`** - registers the function so Zingg can find it -2. **hash functions config** - tells Zingg which functions are available to evaluate - -Zingg evaluates all registered functions against your training data and selects the best combination automatically. - -{% hint style="success" icon="right-long" %} -Custom blocking functions apply to both Community and Enterprise editions. The function evaluation happens during `train` . You do not need to re-run `findTrainingData` or `label` . -{% endhint %} - -#### Custom similarity functions - -For similarity, you can define your comparison measures alongside Zingg's built-in ones. Each `dataType` has predefined features—for example, the `string` type with `FUZZY` match is configured for Affine and Jaro-Winkler string comparison. - -You can define your comparison functions and register them so Zingg uses them as additional features for the classifier. The classifier then determines the best weight for each feature, including your custom one, based on your labeled training data. - -_**LINK TO BE ADDED - GitHub Zingg repository custom similarity example (add link to**** ****`github.com/zinggAI/zingg`**** ****once confirmed by team)**_ - -Custom similarity and blocking function implementation requires Java/Scala code changes to the Zingg JAR. These are advanced customizations. For most use cases, adjusting match types, stopwords, and training data is sufficient before reaching this step. - -{% hint style="success" icon="right-long" %} -**Read more**: Before writing custom functions, try switching the field ordering strategy at each blocking-tree node → [Blocking Strategies: DEFAULT vs WIDER](blocking-strategy.md) (Enterprise only) -{% endhint %} - diff --git a/docs/tuning/improve-accuracy/README.md b/docs/tuning/improve-accuracy/README.md index 2e31942f4..f68fcb036 100644 --- a/docs/tuning/improve-accuracy/README.md +++ b/docs/tuning/improve-accuracy/README.md @@ -21,8 +21,8 @@ The most reliable way to improve accuracy is to label more pairs. Return to the {% hint style="success" icon="right-long" %} **Read more**: -* Label training pairs - [Label training pairs](../../running-zingg/label-training-pairs.md) -* Create training data - [Create training data ](../../running-zingg/create-training-data.md) +* Label training pairs - [Label training pairs](../../running-zingg/label-training-pairs.md) +* Create training data - [Create training data](../../running-zingg/create-training-data.md) {% endhint %} ### Step 2: Check your field match types @@ -56,5 +56,4 @@ If Zingg is missing matches that you know exist (false negatives), the blocking ### Step 5: Custom blocking and similarity For advanced use cases, you can define your own blocking and similarity functions to replace\ -or extend Zingg's built-in approaches. See [Custom Blocking and Similarity](../custom-blocking-and-similarity.md) for advanced tuning approaches. - +or extend Zingg's built-in approaches. See [Custom Blocking and Similarity](/broken/pages/DWdyf7az3MmhJaVca3k7) for advanced tuning approaches. From f2dc656acc9923c10c711cd927989eb39c4d4d44 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 12:44:38 +0000 Subject: [PATCH 090/144] GITBOOK-365: No subject --- ...g-is-built-to-solve-and-why-they-are-harder-than-it-looks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index 39170ca52..fdc9a7e5d 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -12,6 +12,6 @@ Entity resolution at scale is not a data quality problem. It is an algorithmic c | **The variation problem - why rules always break** | Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance. | | **The confidence problem - why binary match/no-match is not enough** |

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely.Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.Read more:

| | **The incremental new data problem** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.Read more:

| -| **The data drift problem - why a model trained once decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes.Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

| +| **The data drift problem - why a model trained once decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

|
From b5fc096bc985627fea43cb8a68cf0d431e96c2e6 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 16:26:00 +0000 Subject: [PATCH 091/144] GITBOOK-367: No subject --- docs/SUMMARY.md | 4 ++-- .../concept-glossary.md | 18 +++++++++--------- docs/running-zingg/pass-through.md | 2 +- docs/running-zingg/step-by-step-guide.md | 4 ++-- .../community-vs-enterprise/README.md | 2 +- .../how-zingg-learns/field-definition.md | 2 +- .../how-zingg-learns/match-types/README.md | 6 ++---- docs/zingg-concepts/the-graph-algorithm.md | 2 +- docs/zingg-concepts/z-cluster-and-zingg-id.md | 2 +- .../deterministic-vs-probabilistic-matching.md | 8 +++----- 10 files changed, 23 insertions(+), 27 deletions(-) rename docs/{zingg-concepts => frequently-asked-questions}/concept-glossary.md (92%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 80f84264a..6f9113f66 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -16,7 +16,7 @@ * [Blocking Model](zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) * [Similarity Model](zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md) * [Field Definition](zingg-concepts/how-zingg-learns/field-definition.md) - * [Match Types](zingg-concepts/how-zingg-learns/match-types/README.md) + * [Match Type](zingg-concepts/how-zingg-learns/match-types/README.md) * [FUZZY Match](zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md) * [FUZZY\_OPTIMISED Match](zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md) * [EXACT Match](zingg-concepts/how-zingg-learns/match-types/exact-match.md) @@ -39,7 +39,6 @@ * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) * [Zingg Pipes](zingg-concepts/zingg-pipes.md) * [Zingg Phases](zingg-concepts/zingg-phases.md) -* [Frequently Asked Questions](zingg-concepts/concept-glossary.md) * [Platform Infrastructure vs Data Platform](zingg-concepts/platform-infrastructure-vs-data-platform.md) ## Running Zingg @@ -140,6 +139,7 @@ ## Frequently Asked Questions * [Frequently Asked Questions](frequently-asked-questions/frequently-asked-questions.md) +* [Frequently Asked Questions](frequently-asked-questions/concept-glossary.md) ## Security and Privacy diff --git a/docs/zingg-concepts/concept-glossary.md b/docs/frequently-asked-questions/concept-glossary.md similarity index 92% rename from docs/zingg-concepts/concept-glossary.md rename to docs/frequently-asked-questions/concept-glossary.md index f22042b35..4d3255c12 100644 --- a/docs/zingg-concepts/concept-glossary.md +++ b/docs/frequently-asked-questions/concept-glossary.md @@ -36,7 +36,7 @@ The process by which Zingg builds its matching model from your feedback rather t typically enough to train a high-accuracy model on datasets of 100,000+ records. {% hint style="success" icon="right-long" %} -**Read more:** [How Zingg learns](how-zingg-learns/) | [Label training pairs](../running-zingg/label-training-pairs.md) +**Read more:** [How Zingg learns](../zingg-concepts/how-zingg-learns/) | [Label training pairs](../running-zingg/label-training-pairs.md) {% endhint %} @@ -51,7 +51,7 @@ Probabilistic matching handles variations in names, addresses, and other messy r \&#xNAN;_Available in all editions_ {% hint style="success" icon="right-long" %} -**Read more:** [Deterministic vs probabilistic matching](zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) +**Read more:** [Deterministic vs probabilistic matching](../zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) {% endhint %} @@ -66,7 +66,7 @@ Both approaches run in a single flow; deterministic rules are evaluated first, a \&#xNAN;_**Enterprise only**_ {% hint style="success" icon="right-long" %} -**Read more:** [Deterministic vs probabilistic matching](zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) +**Read more:** [Deterministic vs probabilistic matching](../zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) {% endhint %} @@ -80,7 +80,7 @@ The logic that turns pairwise match decisions into complete clusters. If Record Zingg uses a graph clustering algorithm to apply this logic correctly without creating false chains from weak indirect matches. This is why records in a cluster can appear at varying confidence levels they matched transitively, not always directly. {% hint style="success" icon="right-long" %} -**Read more:** [How Zingg learns](how-zingg-learns/) +**Read more:** [How Zingg learns](../zingg-concepts/how-zingg-learns/) {% endhint %} @@ -96,7 +96,7 @@ Learn how field definitions, match types, and related settings influence matchin Match types are the combination of similarity functions assigned to each field, telling Zingg how to compare values in that field across records. Zingg provides match types for fuzzy comparison, exact match, email, numeric, text, and several specialized types for alphabet-only or null handling. -Each field receives one match type. The full list of all match types; what they do, when to use each, and examples on [Match Types](how-zingg-learns/match-types/). +Each field receives one match type. The full list of all match types; what they do, when to use each, and examples on [Match Types](../zingg-concepts/how-zingg-learns/match-types/). A quick reference for the most common fields: @@ -158,7 +158,7 @@ The cluster identifier used in Zingg Community (Open Source). Records Zingg grou `Z Cluster` is non-persistent; re-running the job may produce different IDs for the same data. It cannot be safely referenced in downstream systems across runs. {% hint style="success" icon="right-long" %} -**Read more:** [How Zingg learns](how-zingg-learns/) +**Read more:** [How Zingg learns](../zingg-concepts/how-zingg-learns/) {% endhint %} @@ -172,7 +172,7 @@ A globally unique, persistent identifier assigned to each resolved entity in Zin Unlike Z Cluster, the Zingg ID does not change between runs. The downstream systems can store and reference it with confidence it will remain stable across incremental updates. _**Enterprise only**_ {% hint style="success" icon="right-long" %} -**Read more:** [Z Cluster and Zingg ID](z-cluster-and-zingg-id.md) +**Read more:** [Z Cluster and Zingg ID](../zingg-concepts/z-cluster-and-zingg-id.md) {% endhint %} @@ -187,7 +187,7 @@ Zingg's underlying data structure representing resolved entities and the relatio * In Enterprise, it is persistent and updated incrementally: new and changed records are incorporated without a full re-run, and Zingg IDs serve as stable node identifiers within it. {% hint style="success" icon="right-long" %} -**Read more:** [Identity graph](identity-graph.md) | [Run incremental matching](../running-zingg/run-incremental-matching.md) +**Read more:** [Identity graph](../zingg-concepts/identity-graph.md) | [Run incremental matching](../running-zingg/run-incremental-matching.md) {% endhint %} @@ -201,7 +201,7 @@ The single, authoritative version of an entity is built by combining the best av For example, a retail customer appears in an e-commerce platform, a loyalty program, and a support system under slightly different names and addresses. The golden record merges the most complete and reliable field values from each into a single trusted profile. Zingg identifies which records belong together and the golden record is typically constructed downstream in a data platform or MDM layer that consumes them. {% hint style="success" icon="right-long" %} -**Read more:** [What is Zingg](../) | [Community vs Enterprise](community-vs-enterprise/). +**Read more:** [What is Zingg](../) | [Community vs Enterprise](../zingg-concepts/community-vs-enterprise/). {% endhint %} diff --git a/docs/running-zingg/pass-through.md b/docs/running-zingg/pass-through.md index aa2e9c932..83cb99e42 100644 --- a/docs/running-zingg/pass-through.md +++ b/docs/running-zingg/pass-through.md @@ -99,6 +99,6 @@ _**CHECK WITH SONAL - Enterprise Snowflake content for this topic to be provivde {% hint style="success" icon="right-long" %} **Read more**: -* Pass Through concept in the glossary - [Concepts glossary](../zingg-concepts/concept-glossary.md) +* Pass Through concept in the glossary - [Concepts glossary](../frequently-asked-questions/concept-glossary.md) * Configure Zingg where Pass Through is set up - [Configure Zingg](configure-zingg.md) {% endhint %} diff --git a/docs/running-zingg/step-by-step-guide.md b/docs/running-zingg/step-by-step-guide.md index aa0d8e331..4bb375e65 100644 --- a/docs/running-zingg/step-by-step-guide.md +++ b/docs/running-zingg/step-by-step-guide.md @@ -11,7 +11,7 @@ Zingg works in phases. Each phase performs a specific task and produces an outpu The phases below represent the complete workflow from first setup to production matching. This page describes the workflow you follow once Zingg is installed. {% hint style="success" icon="right-long" %} -This page describes the workflow you follow once Zingg is installed. \ +This page describes the workflow you follow once Zingg is installed.\ For installing Zingg on your platform → [Install Zingg](install-zingg.md). New to entity resolution or want to understand the problem space before diving in? → E[ntity Resolution](../zingg-concepts/entity-resolution/) @@ -128,7 +128,7 @@ full Python API support. [Talk to us about Enterprise](https://www.zingg.ai/comp {% hint style="success" icon="right-long" %} **Read more**: -* Understanding the concepts behind each phase - [Overview section](../README.md) | [Concepts glossary](../zingg-concepts/concept-glossary.md) +* Understanding the concepts behind each phase - [Overview section](../) | [Concepts glossary](../frequently-asked-questions/concept-glossary.md) * [Configure Zingg](configure-zingg.md) - full configuration reference * [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) - every output column and how to read scores * [Zingg\_ID and Z\_Cluster](../zingg-concepts/z-cluster-and-zingg-id.md) - Community vs Enterprise identifiers in your output diff --git a/docs/zingg-concepts/community-vs-enterprise/README.md b/docs/zingg-concepts/community-vs-enterprise/README.md index 5469220ae..f89a7318e 100644 --- a/docs/zingg-concepts/community-vs-enterprise/README.md +++ b/docs/zingg-concepts/community-vs-enterprise/README.md @@ -82,6 +82,6 @@ Ready to move to Enterprise? This is the foundational difference from Community, where `Z_Cluster` is non-persistent and cannot be safely stored downstream. -* [Zingg ID](../concept-glossary.md#zingg-id) +* [Zingg ID](../../frequently-asked-questions/concept-glossary.md#zingg-id) * [Z Cluster and Zingg ID](../z-cluster-and-zingg-id.md) {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/field-definition.md b/docs/zingg-concepts/how-zingg-learns/field-definition.md index 514234233..9f2d71b26 100644 --- a/docs/zingg-concepts/how-zingg-learns/field-definition.md +++ b/docs/zingg-concepts/how-zingg-learns/field-definition.md @@ -4,7 +4,7 @@ description: Choosing what to match # Field Definition -Field Definition is Zingg is the configuration object that tells Zingg which fields to use for matching and how to compare them. +Field Definition is the configuration object that tells Zingg which fields to use for matching and how to compare them. Each field definition has four attributes: `fieldName` (the column name), `fields` (same as `fieldName` for now), `dataType` (string, integer, double, etc.), and `matchType` (the similarity functions to apply). diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/README.md b/docs/zingg-concepts/how-zingg-learns/match-types/README.md index 4f29506c2..13963ac06 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/README.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/README.md @@ -1,10 +1,8 @@ --- -description: >- - The 12 similarity functions Zingg supports what each does, when to use it, and - which are Enterprise only. +description: Abstraction for similarity algorithms in human understandable terms --- -# Match Types +# Match Type Every field in your Zingg configuration gets a match type assigned to it. The match type tells\ Zingg which similarity function to apply when comparing values in that field across two records. Each `dataType` has predefined features for the given `matchType`. For example, the `string` type with `FUZZY` match is configured for Affine and Jaro-Winkler string comparison. diff --git a/docs/zingg-concepts/the-graph-algorithm.md b/docs/zingg-concepts/the-graph-algorithm.md index 618620641..2aadafaae 100644 --- a/docs/zingg-concepts/the-graph-algorithm.md +++ b/docs/zingg-concepts/the-graph-algorithm.md @@ -1,5 +1,5 @@ --- -description: from pairs to clusters +description: From pairs to clusters --- # The graph algorithm diff --git a/docs/zingg-concepts/z-cluster-and-zingg-id.md b/docs/zingg-concepts/z-cluster-and-zingg-id.md index e153bc3be..92837ae18 100644 --- a/docs/zingg-concepts/z-cluster-and-zingg-id.md +++ b/docs/zingg-concepts/z-cluster-and-zingg-id.md @@ -65,5 +65,5 @@ Use this column to: * [Identity Graph](identity-graph.md) - how Zingg's resolved entities form a graph structure * [Run Incremental Matching](../running-zingg/run-incremental-matching.md) - how Zingg IDs persist when new records arrive * [Reassign Zingg ID](../running-zingg/reassign-zingg-id.md) - preserve Zingg IDs across model retraining or platform migration -* [Concept Glossary](concept-glossary.md) - every Zingg term in one place +* [Concept Glossary](../frequently-asked-questions/concept-glossary.md) - every Zingg term in one place {% endhint %} diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md b/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md index c2a025901..248b64220 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md @@ -20,13 +20,11 @@ _You do not write rules. You label examples. Zingg learns the rest._ ### Deterministic matching -Deterministic matching lets you define hard rules for cases where certain field combinations should always produce a match, regardless of the probabilistic score. When your data contains reliable unique identifiers, a national ID, an email, or a combination like first name, email plus date of birth, determinstic matching can bring them together. - - +Deterministic matching lets you define hard rules for cases where certain field combinations should always produce a match, regardless of the probabilistic score. When your data contains reliable unique identifiers, a national ID, an email, or a combination like first name, email plus date of birth, determinstic matching can bring them together. ### How Zingg matches prbabilistically and deterministically -Zingg Enterprise applies deterministic rules first. If two records share the same combination of identifiers you specify, Zingg treats that pair as a match with a score of 1, bypassing the ML model entirely for that pair. Pairs resolved deterministically are not re-evaluated probabilistically. Pairs that do not satisfy any deterministic condition fall through to the probabilistic model. All matching pairs get rolled into clusters. +Zingg Enterprise applies deterministic rules first. If two records share the same combination of identifiers you specify, Zingg treats that pair as a match with a score of 1, bypassing the ML model entirely for that pair. Pairs resolved deterministically are not re-evaluated probabilistically. Pairs that do not satisfy any deterministic condition fall through to the probabilistic model. All matching pairs get rolled into clusters. Probabilistic and deterministic matching in Zingg Enterprise runs both in a single flow, so you do not have to choose between them. @@ -63,7 +61,7 @@ Do not use deterministic matching on fields that are frequently incomplete or in If you are unsure, start with probabilistic matching only. Add deterministic rules once you have reviewed match output and identified high-confidence identifier combinations in your data. {% hint style="success" icon="right-long" %} -**Read more:** [Configure Zingg](../../running-zingg/configure-zingg.md) | [Concepts glossary](../concept-glossary.md) +**Read more:** [Configure Zingg](../../running-zingg/configure-zingg.md) | [Concepts glossary](../../frequently-asked-questions/concept-glossary.md) {% endhint %} From 086285be2683b42a136ada012ce0e488f32bf3f9 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 17:45:59 +0000 Subject: [PATCH 092/144] GITBOOK-368: No subject --- docs/SUMMARY.md | 1 + .../zingg-entity-resolution-platform/the-graph-algorithm.md | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 docs/zingg-concepts/zingg-entity-resolution-platform/the-graph-algorithm.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 6f9113f66..ded50ab70 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -10,6 +10,7 @@ * [Deterministic vs Probabilistic Matching](zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) * [Deduplication, linking and resolving](zingg-concepts/zingg-entity-resolution-platform/deduplication-linking-and-resolving.md) * [Incremental Flow](zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md) + * [The graph algorithm](zingg-concepts/zingg-entity-resolution-platform/the-graph-algorithm.md) * [How Zingg Learns](zingg-concepts/how-zingg-learns/README.md) * [Zingg Active Learning](zingg-concepts/how-zingg-learns/zingg-active-learning.md) * [Zingg Models](zingg-concepts/how-zingg-learns/zingg-models/README.md) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-graph-algorithm.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-graph-algorithm.md new file mode 100644 index 000000000..5193acd94 --- /dev/null +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-graph-algorithm.md @@ -0,0 +1,6 @@ +--- +description: Pair wise matching is not enough +--- + +# The graph algorithm + From adfc1bd41e39c0bb3646e8cc26d59423a30cfb4e Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 18:00:54 +0000 Subject: [PATCH 093/144] GITBOOK-369: No subject --- docs/SUMMARY.md | 6 ++---- docs/zingg-concepts/the-graph-algorithm.md | 21 ------------------- .../incremental-flow.md | 1 - .../the-graph-algorithm.md | 6 ------ ...e-and-why-they-are-harder-than-it-looks.md | 15 ++++++------- 5 files changed, 10 insertions(+), 39 deletions(-) delete mode 100644 docs/zingg-concepts/the-graph-algorithm.md delete mode 100644 docs/zingg-concepts/zingg-entity-resolution-platform/the-graph-algorithm.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index ded50ab70..3d0c142cd 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -10,7 +10,6 @@ * [Deterministic vs Probabilistic Matching](zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) * [Deduplication, linking and resolving](zingg-concepts/zingg-entity-resolution-platform/deduplication-linking-and-resolving.md) * [Incremental Flow](zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md) - * [The graph algorithm](zingg-concepts/zingg-entity-resolution-platform/the-graph-algorithm.md) * [How Zingg Learns](zingg-concepts/how-zingg-learns/README.md) * [Zingg Active Learning](zingg-concepts/how-zingg-learns/zingg-active-learning.md) * [Zingg Models](zingg-concepts/how-zingg-learns/zingg-models/README.md) @@ -33,14 +32,13 @@ * [NULL\_OR\_BLANK Match](zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md) * [DONT\_USE Match](zingg-concepts/how-zingg-learns/match-types/dont_use-match.md) * [MAPPING\_(FILENAME) Match](zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md) -* [The graph algorithm](zingg-concepts/the-graph-algorithm.md) * [Identity Graph](zingg-concepts/identity-graph.md) * [Z Cluster and Zingg ID](zingg-concepts/z-cluster-and-zingg-id.md) -* [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) - * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) * [Zingg Pipes](zingg-concepts/zingg-pipes.md) * [Zingg Phases](zingg-concepts/zingg-phases.md) * [Platform Infrastructure vs Data Platform](zingg-concepts/platform-infrastructure-vs-data-platform.md) +* [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) + * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) ## Running Zingg diff --git a/docs/zingg-concepts/the-graph-algorithm.md b/docs/zingg-concepts/the-graph-algorithm.md deleted file mode 100644 index 2aadafaae..000000000 --- a/docs/zingg-concepts/the-graph-algorithm.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -description: From pairs to clusters ---- - -# The graph algorithm - -After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure. - -If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared. - -This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations. - -In Community, clusters are assigned a `Z Cluster` that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent `Zingg ID` (GUID) that remains stable across runs, incremental updates, and model changes. - -{% hint style="success" icon="right-long" %} -**Read more**: - -* [Identity Graph](identity-graph.md) -* [Z Cluster ID vs Zingg ID](z-cluster-and-zingg-id.md) -* [Zingg ID](/broken/pages/9QpDFW20AMt0UJ4cEW6b) -{% endhint %} diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md b/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md index 8d2f034c0..e67d1a46a 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md @@ -6,7 +6,6 @@ description: When it is not enough to match once Data in enteprise systems gets regularly updated. The business expands, and new customer records come in. Entities change - people move, get new phones, register new businesses. Some systems cease and records get deleted. Matching once is powerful, but it ceases to be enough in such cases. A full rematch is costly, and loses state. -\ The process of updating the identity graph with new, changed, or deleted records without re-running the full match across the entire dataset is known as incremental flow in Zingg. Zingg incorporates incoming records into existing clusters, handles cluster merges and unmerges automatically, generates new Zingg IDs for records that do not match any existing cluster, and preserves human-approved decisions so they are not overridden. Many tools claim incremental capability but cannot maintain match quality and stable IDs simultaneously. This feature is a key Zingg Enterprise differentiator. _Enterprise only_. diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-graph-algorithm.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-graph-algorithm.md deleted file mode 100644 index 5193acd94..000000000 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-graph-algorithm.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -description: Pair wise matching is not enough ---- - -# The graph algorithm - diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index fdc9a7e5d..87b0530ce 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -6,12 +6,13 @@ description: And why they are harder than it looks Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem. And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. -| Problem | Description | -| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **The N² comparison problem** |

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

| -| **The variation problem - why rules always break** | Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance. | -| **The confidence problem - why binary match/no-match is not enough** |

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely.Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.Read more:

| -| **The incremental new data problem** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.Read more:

| -| **The data drift problem - why a model trained once decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

| +| Problem | Description | +| -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **The N² comparison problem** |

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

| +| **The variation problem - why rules always break** | Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance. | +| **The confidence problem - why binary match/no-match is not enough** |

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

| +| **The pairs are not enough problem** |

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

| +| **The incremental new data problem** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.Read more:

| +| **The data drift problem - why a model trained once decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

|
From af24f2199f5c0d7f926b9ebd81ca010b3e490fd9 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 18:09:25 +0000 Subject: [PATCH 094/144] GITBOOK-370: No subject --- docs/SUMMARY.md | 7 +++++-- .../community-vs-enterprise/README.md | 4 ++-- ...ing-from-community-to-enterprise-content-to-be-added.md | 0 docs/frequently-asked-questions/concept-glossary.md | 2 +- docs/zingg-concepts/entity-resolution/README.md | 2 +- docs/zingg-concepts/identity-graph.md | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) rename docs/{zingg-concepts => community-and-enterprise-editions}/community-vs-enterprise/README.md (96%) rename docs/{zingg-concepts => community-and-enterprise-editions}/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md (100%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 3d0c142cd..2f6b2df96 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -37,8 +37,11 @@ * [Zingg Pipes](zingg-concepts/zingg-pipes.md) * [Zingg Phases](zingg-concepts/zingg-phases.md) * [Platform Infrastructure vs Data Platform](zingg-concepts/platform-infrastructure-vs-data-platform.md) -* [Community vs Enterprise](zingg-concepts/community-vs-enterprise/README.md) - * [Migrating from Community to Enterprise (Content to be Added)](zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) + +## Community And Enterprise Editions + +* [Community vs Enterprise](community-and-enterprise-editions/community-vs-enterprise/README.md) + * [Migrating from Community to Enterprise (Content to be Added)](community-and-enterprise-editions/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) ## Running Zingg diff --git a/docs/zingg-concepts/community-vs-enterprise/README.md b/docs/community-and-enterprise-editions/community-vs-enterprise/README.md similarity index 96% rename from docs/zingg-concepts/community-vs-enterprise/README.md rename to docs/community-and-enterprise-editions/community-vs-enterprise/README.md index f89a7318e..06ea5b316 100644 --- a/docs/zingg-concepts/community-vs-enterprise/README.md +++ b/docs/community-and-enterprise-editions/community-vs-enterprise/README.md @@ -31,7 +31,7 @@ The step from Community to Enterprise Lite is a step from evaluation to producti Enterprise Lite also gives you: * [**Incremental matching**](../../running-zingg/run-incremental-matching.md) **-** update the identity graph as new records arrive, without re-running on your entire dataset -* [**Deterministic matching**](../zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) **-** combine hard rules for known trusted identifiers (SSN, tax ID, email) with probabilistic ML in a single flow +* [**Deterministic matching**](../../zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) **-** combine hard rules for known trusted identifiers (SSN, tax ID, email) with probabilistic ML in a single flow * [**Pass Through**](../../running-zingg/pass-through.md) **-** records that are incomplete or unsuitable for matching still appear in your output and receive a Zingg ID for governance and compliance * [**Production-grade notebooks**](../../platform-guides/platform-guide-for-azure-databricks.md) **-** a 7-notebook sequence, one per workflow phase, built for repeatable production runs @@ -83,5 +83,5 @@ Ready to move to Enterprise? This is the foundational difference from Community, where `Z_Cluster` is non-persistent and cannot be safely stored downstream. * [Zingg ID](../../frequently-asked-questions/concept-glossary.md#zingg-id) -* [Z Cluster and Zingg ID](../z-cluster-and-zingg-id.md) +* [Z Cluster and Zingg ID](../../zingg-concepts/z-cluster-and-zingg-id.md) {% endhint %} diff --git a/docs/zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md b/docs/community-and-enterprise-editions/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md similarity index 100% rename from docs/zingg-concepts/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md rename to docs/community-and-enterprise-editions/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md diff --git a/docs/frequently-asked-questions/concept-glossary.md b/docs/frequently-asked-questions/concept-glossary.md index 4d3255c12..f754d8b15 100644 --- a/docs/frequently-asked-questions/concept-glossary.md +++ b/docs/frequently-asked-questions/concept-glossary.md @@ -201,7 +201,7 @@ The single, authoritative version of an entity is built by combining the best av For example, a retail customer appears in an e-commerce platform, a loyalty program, and a support system under slightly different names and addresses. The golden record merges the most complete and reliable field values from each into a single trusted profile. Zingg identifies which records belong together and the golden record is typically constructed downstream in a data platform or MDM layer that consumes them. {% hint style="success" icon="right-long" %} -**Read more:** [What is Zingg](../) | [Community vs Enterprise](../zingg-concepts/community-vs-enterprise/). +**Read more:** [What is Zingg](../) | [Community vs Enterprise](../community-and-enterprise-editions/community-vs-enterprise/). {% endhint %} diff --git a/docs/zingg-concepts/entity-resolution/README.md b/docs/zingg-concepts/entity-resolution/README.md index e3e19dc29..a64d5bcb2 100644 --- a/docs/zingg-concepts/entity-resolution/README.md +++ b/docs/zingg-concepts/entity-resolution/README.md @@ -157,5 +157,5 @@ Need entity resolution at production scale with persistent identity and incremen processing? * [Talk to us about Enterprise](https://www.zingg.ai/company/contact/contact) -* [Community vs Enterprise](../community-vs-enterprise/)
+* [Community vs Enterprise](../../community-and-enterprise-editions/community-vs-enterprise/)
{% endhint %} diff --git a/docs/zingg-concepts/identity-graph.md b/docs/zingg-concepts/identity-graph.md index d6510f17c..63637bf55 100644 --- a/docs/zingg-concepts/identity-graph.md +++ b/docs/zingg-concepts/identity-graph.md @@ -46,5 +46,5 @@ Enterprise gives you the same graph with stable `Zingg IDs` , so the graph can g * [Z Cluster vs Zingg ID](z-cluster-and-zingg-id.md) * [Zingg Models](how-zingg-learns/zingg-models/) * [Run incremental matching](../running-zingg/run-incremental-matching.md) -* [Community Vs Enterprise](community-vs-enterprise/) +* [Community Vs Enterprise](../community-and-enterprise-editions/community-vs-enterprise/) {% endhint %} From 4702a2057bc8e9e468aa5927081fab7bdff393e1 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 18:10:40 +0000 Subject: [PATCH 095/144] GITBOOK-371: No subject --- docs/zingg-concepts/platform-infrastructure-vs-data-platform.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md b/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md index 83fc31b97..8628dc438 100644 --- a/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md +++ b/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md @@ -1,5 +1,5 @@ --- -description: Where Zingg runs, which data is resolves +description: Where Zingg runs, which data it resolves --- # Platform Infrastructure vs Data Platform From 647bc45777d21d1baaa28e8c54837c2c0a1218b7 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Tue, 18 Aug 2026 18:11:31 +0000 Subject: [PATCH 096/144] GITBOOK-372: No subject --- docs/zingg-concepts/zingg-pipes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-pipes.md b/docs/zingg-concepts/zingg-pipes.md index dd2e28d1e..351c98f75 100644 --- a/docs/zingg-concepts/zingg-pipes.md +++ b/docs/zingg-concepts/zingg-pipes.md @@ -4,7 +4,7 @@ description: Connecting to data # Zingg Pipes -Zingg's abstraction for connecting to data. A pipe encapsulates the source or destination of records; a delta file path, a Snowflake table, a UC table, an RDBMS dataset. Different pipes are configured by passing the format string in the configuration. +Pipes are Zingg's abstraction for the data store. A pipe encapsulates the source or destination of records; a delta file path, a Snowflake table, a UC table, an RDBMS dataset. Different pipes are configured by passing the format string in the configuration. {% hint style="success" icon="right-long" %} **Read more**: [Pipes and Data Connections](../connect-your-data/pipes-and-data-connections.md) From 4c325bbce91572dc0496ca9f25b6bd3d69783f9a Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 01:32:50 +0000 Subject: [PATCH 097/144] GITBOOK-373: No subject --- docs/reference/configuration-schema.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/reference/configuration-schema.md b/docs/reference/configuration-schema.md index 04d6fde8f..561a2f5ee 100644 --- a/docs/reference/configuration-schema.md +++ b/docs/reference/configuration-schema.md @@ -20,16 +20,12 @@ This page is the reference. Every Zingg configuration parameter, JSON key and Py Each entry in the `fieldDefinition` array defines one field in your input schema. -
ParameterTypeEditionDescription
fieldNamestringAll editionsName of the field in your input dataset. Must match exactly.
matchTypeenumAll editionsHow to compare this field. See Match Types for all values.
fieldsstringAll editionsThe field to use for comparison. Keep the same as fieldName for standard use. Can reference a different column name if the field you want to compare appears under a different name in the input schema. For now, keep this the same as fieldName unless specifically advised otherwise.
dataTypestringAll editionsSpark SQL data type - string, integer, double, date, timestamp.
stopWordsstring (path)All editions, optionalPath to a CSV file of stopwords for this field. One word per row. The file must have a header row. Generated by the recommend phase.
primaryKeybooleanEnterprise only, optionalMarks this field as the primary key. Used by runIncremental to identify records uniquely across runs and by reassignZinggId for ID preservation. Set true on exactly one field per config.
postProcessorsstringEnterprise only, optionalStandardise postprocessor reference. Format: STANDARDISE_<basename> where <basename>.json is the mapping file. Normalises field values to canonical form after matching.
MappingMatchTypenested objectEnterprise only, optionalUser-supplied lookup file for nickname/abbreviation matching. See MAPPING match type for the mapping file format and rules.
+
ParameterTypeEditionDescription
fieldNamestringAll editionsName of the field in your input dataset. Must match exactly.
matchTypeenumAll editionsHow to compare this field. See Match Types for all values.
fieldsstringAll editionsThe field to use for comparison. Keep the same as fieldName for standard use. Can reference a different column name if the field you want to compare appears under a different name in the input schema. For now, keep this the same as fieldName unless specifically advised otherwise.
dataTypestringAll editionsSpark SQL data type - string, integer, double, date, timestamp.
stopWordsstring (path)All editions, optionalPath to a CSV file of stopwords for this field. One word per row. The file must have a header row. Generated by the recommend phase.
primaryKeybooleanEnterprise only, optionalMarks this field as the primary key. Used by runIncremental to identify records uniquely across runs and by reassignZinggId for ID preservation. Set true on exactly one field per config.
postProcessorsstringEnterprise only, optionalStandardise postprocessor reference. Format: STANDARDISE_<basename> where <basename>.json is the mapping file. Normalises field values to canonical form after matching.
-### `data` (input pipe) +### `data/output/outputStats` (pipe)
ParameterTypeEditionDescription
namestringAll editionsLogical name for this pipe. Used in logging.
formatstringAll editionsConnector format. See Connect Your Data for all supported formats.
propsobjectAll editionsConnector-specific properties. Keys depend on the format. See Connect Your Data for props per connector.
schemastringAll editionsSpark SQL schema string. Required for CSV. Format: "field1 type1, field2 type2, ...".
-### `output` (output pipe) - -
ParameterTypeEditionDescription
namestringAll editionsLogical name for this output pipe.
formatstringAll editionsConnector format. See Connect Your Data for supported output formats.
propsobjectAll editionsConnector-specific properties. Keys depend on the format.
- ### `outputStats` (statistics pipe) Uses the same `format`, `name`, and `props` pattern as `data` and `output`. The path must contain the `$ZINGG_DYNAMIC_STAT_NAME` placeholder, which is replaced at runtime with `SUMMARY`, `CLUSTER`, or `RECORD` depending on the statistics type written. If not configured, statistics are not written and the run proceeds normally. From 390a93738fb8cdd235bf2160d1026e2174496353 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 02:24:17 +0000 Subject: [PATCH 098/144] GITBOOK-374: No subject --- ...g-is-built-to-solve-and-why-they-are-harder-than-it-looks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index 87b0530ce..3b6bad4bb 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -12,7 +12,7 @@ Entity resolution at scale is not a data quality problem. It is an algorithmic c | **The variation problem - why rules always break** | Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance. | | **The confidence problem - why binary match/no-match is not enough** |

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

| | **The pairs are not enough problem** |

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

| -| **The incremental new data problem** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons.First, re-running the full match is computationally expensive. At one million records, a full match takes hours. Doing it every time a thousand new records arrive is not viable.Second, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.Read more:

| +| **The incremental new, updated and deleted data problem** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

| | **The data drift problem - why a model trained once decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

|
From 1f6fcbc646e0c0c20eb2f70c9829b5c88972f0d4 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 02:28:24 +0000 Subject: [PATCH 099/144] GITBOOK-375: No subject --- .../deterministic-vs-probabilistic-matching.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md b/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md index 248b64220..94526699d 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md @@ -20,11 +20,11 @@ _You do not write rules. You label examples. Zingg learns the rest._ ### Deterministic matching -Deterministic matching lets you define hard rules for cases where certain field combinations should always produce a match, regardless of the probabilistic score. When your data contains reliable unique identifiers, a national ID, an email, or a combination like first name, email plus date of birth, determinstic matching can bring them together. +Deterministic matching is only available in the Enterprise Edition. It lets you define hard rules for cases where certain field combinations should always produce a match, regardless of the probabilistic score. When your data contains reliable unique identifiers, a national ID, an email, or a combination like first name, email plus date of birth, determinstic matching can bring them together. Zingg Enterprise can apply any combination of deterministic rules like (ssn) or (email and dob) or (passport number and dob). -### How Zingg matches prbabilistically and deterministically +### How Zingg matches probabilistically AND deterministically -Zingg Enterprise applies deterministic rules first. If two records share the same combination of identifiers you specify, Zingg treats that pair as a match with a score of 1, bypassing the ML model entirely for that pair. Pairs resolved deterministically are not re-evaluated probabilistically. Pairs that do not satisfy any deterministic condition fall through to the probabilistic model. All matching pairs get rolled into clusters. +Zingg applies deterministic rules first. If two records share the same combination of identifiers you specify, Zingg treats that pair as a match with a score of 1, bypassing the ML model entirely for that pair. Pairs resolved deterministically are not re-evaluated probabilistically. Pairs that do not satisfy any deterministic condition fall through to the probabilistic model. All matching pairs get rolled into clusters. Probabilistic and deterministic matching in Zingg Enterprise runs both in a single flow, so you do not have to choose between them. From 024f12916e0f6dbfc5bb6090cd914e1e7700d6c4 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 02:51:45 +0000 Subject: [PATCH 100/144] GITBOOK-376: No subject --- ...gg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index 3b6bad4bb..6f92fd604 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -14,5 +14,6 @@ Entity resolution at scale is not a data quality problem. It is an algorithmic c | **The pairs are not enough problem** |

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

| | **The incremental new, updated and deleted data problem** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

| | **The data drift problem - why a model trained once decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

| +| **The governance gap** | Explaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching. |
From 84a6a38277e7be3892ab9df4fac165ab2d109055 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 03:06:08 +0000 Subject: [PATCH 101/144] GITBOOK-377: No subject --- docs/reference/configuration-schema.md | 2 +- ...g-is-built-to-solve-and-why-they-are-harder-than-it-looks.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/configuration-schema.md b/docs/reference/configuration-schema.md index 561a2f5ee..9a87b4d17 100644 --- a/docs/reference/configuration-schema.md +++ b/docs/reference/configuration-schema.md @@ -22,7 +22,7 @@ Each entry in the `fieldDefinition` array defines one field in your input schema
ParameterTypeEditionDescription
fieldNamestringAll editionsName of the field in your input dataset. Must match exactly.
matchTypeenumAll editionsHow to compare this field. See Match Types for all values.
fieldsstringAll editionsThe field to use for comparison. Keep the same as fieldName for standard use. Can reference a different column name if the field you want to compare appears under a different name in the input schema. For now, keep this the same as fieldName unless specifically advised otherwise.
dataTypestringAll editionsSpark SQL data type - string, integer, double, date, timestamp.
stopWordsstring (path)All editions, optionalPath to a CSV file of stopwords for this field. One word per row. The file must have a header row. Generated by the recommend phase.
primaryKeybooleanEnterprise only, optionalMarks this field as the primary key. Used by runIncremental to identify records uniquely across runs and by reassignZinggId for ID preservation. Set true on exactly one field per config.
postProcessorsstringEnterprise only, optionalStandardise postprocessor reference. Format: STANDARDISE_<basename> where <basename>.json is the mapping file. Normalises field values to canonical form after matching.
-### `data/output/outputStats` (pipe) +### `data/output` (pipe)
ParameterTypeEditionDescription
namestringAll editionsLogical name for this pipe. Used in logging.
formatstringAll editionsConnector format. See Connect Your Data for all supported formats.
propsobjectAll editionsConnector-specific properties. Keys depend on the format. See Connect Your Data for props per connector.
schemastringAll editionsSpark SQL schema string. Required for CSV. Format: "field1 type1, field2 type2, ...".
diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index 6f92fd604..887f5eb7e 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -15,5 +15,6 @@ Entity resolution at scale is not a data quality problem. It is an algorithmic c | **The incremental new, updated and deleted data problem** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

| | **The data drift problem - why a model trained once decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

| | **The governance gap** | Explaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching. | +| **The domain experience** | Some records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further. Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended :smile:) |
From 01b35eacc4f92c858f1ec0be8d03e5ea6d67fe98 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 03:19:20 +0000 Subject: [PATCH 102/144] GITBOOK-379: No subject --- ...-is-built-to-solve-and-why-they-are-harder-than-it-looks.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index 887f5eb7e..23d1a6a95 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -15,6 +15,7 @@ Entity resolution at scale is not a data quality problem. It is an algorithmic c | **The incremental new, updated and deleted data problem** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

| | **The data drift problem - why a model trained once decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

| | **The governance gap** | Explaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching. | -| **The domain experience** | Some records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further. Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended :smile:) | +| **The domain experience** |

Some records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)

| +| | |
From de981993da6b127762f7a4e63f56d72d6c46e025 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 03:19:36 +0000 Subject: [PATCH 103/144] GITBOOK-380: No subject --- ...g-is-built-to-solve-and-why-they-are-harder-than-it-looks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index 23d1a6a95..34269be57 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -15,7 +15,7 @@ Entity resolution at scale is not a data quality problem. It is an algorithmic c | **The incremental new, updated and deleted data problem** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

| | **The data drift problem - why a model trained once decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

| | **The governance gap** | Explaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching. | -| **The domain experience** |

Some records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)

| +| **The domain experience gap** |

Some records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)

| | | |
From 553f43aeb02b72dcd4e22971f2c391a26f06560e Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 05:22:15 +0000 Subject: [PATCH 104/144] GITBOOK-381: No subject --- docs/SUMMARY.md | 2 +- .../{incremental-flow.md => incremental-run.md} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/zingg-concepts/zingg-entity-resolution-platform/{incremental-flow.md => incremental-run.md} (100%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 2f6b2df96..3e07feebc 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -9,7 +9,7 @@ * [The problems Zingg is built to solve](zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md) * [Deterministic vs Probabilistic Matching](zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) * [Deduplication, linking and resolving](zingg-concepts/zingg-entity-resolution-platform/deduplication-linking-and-resolving.md) - * [Incremental Flow](zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md) + * [Incremental Run](zingg-concepts/zingg-entity-resolution-platform/incremental-run.md) * [How Zingg Learns](zingg-concepts/how-zingg-learns/README.md) * [Zingg Active Learning](zingg-concepts/how-zingg-learns/zingg-active-learning.md) * [Zingg Models](zingg-concepts/how-zingg-learns/zingg-models/README.md) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md b/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-run.md similarity index 100% rename from docs/zingg-concepts/zingg-entity-resolution-platform/incremental-flow.md rename to docs/zingg-concepts/zingg-entity-resolution-platform/incremental-run.md From 66c3cbd6f21f6fdde39f88628d844248182b6b24 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 05:43:32 +0000 Subject: [PATCH 105/144] GITBOOK-382: No subject --- .../interpret-output-scores.md | 69 +++++++++++++++++++ .../running-zingg/build-and-save-the-model.md | 18 +++-- .../how-zingg-learns/zingg-models/README.md | 20 +----- .../zingg-models/similarity-model.md | 66 ------------------ ...e-and-why-they-are-harder-than-it-looks.md | 12 +--- 5 files changed, 83 insertions(+), 102 deletions(-) diff --git a/docs/interpreting-results/interpret-output-scores.md b/docs/interpreting-results/interpret-output-scores.md index 1d5eb96ea..b5a3c1b39 100644 --- a/docs/interpreting-results/interpret-output-scores.md +++ b/docs/interpreting-results/interpret-output-scores.md @@ -117,6 +117,75 @@ output.groupBy("ZINGG_ID") \ Reading match output is the same in Community and Enterprise - only the cluster column name differs. Community produces `Z_CLUSTER`. Enterprise produces `ZINGG_ID`. Replace the column name in your code accordingly. {% endhint %} +{% hint style="success" icon="right-long" %} +Ordering clusters by `avg_min` ascending puts the weakest clusters at the top, those are the ones worth reviewing for false positives first. + +**Read more**: + +* [Interpret Output Scores](interpret-output-scores.md) - `Z_MINSCORE` and `Z_MAXSCORE` explained +* [Label Training Pairs](../running-zingg/label-training-pairs.md) - adding training data for missed patterns +* [Verify blocking](../running-zingg/verify-blocking.md) +{% endhint %} + +A small number of targeted labels for the specific pattern being missed is more effective than a large general labeling run. + +Run `findTrainingData` and look for pairs similar to the ones being missed. Label them as `match`. The similarity model needs to see this pattern in the training data to learn it. If the variation that\ +causes the mismatch like a specific abbreviation pattern, a missing field, or a transliteration not represented in your labeled pairs; the model has no basis for scoring it above the threshold. + +#### Add match labels for the missed pattern + +If `verifyBlocking` confirms the pair is reaching the similarity model but still not matching, proceed to the training data fix below. + +Run `verifyBlocking`. If the missed pair is not being blocked together, the similarity model is not the cause. Address the blocking issue first by referring to the [Blocking Model](../zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) page. + +#### Confirm it as a similarity problem, not a blocking problem + +A false negative is a pair of records that represent the same entity but were not placed in the same cluster. The similarity model scored them below the match threshold or the blocking model never allowed them to be compared. + +### Diagnosing false negatives matches that were missed + +
+ +If false positives are widespread, not isolated + +If false positives affect many clusters rather than a specific pattern, check your field match types before adding more training data. + +Fields that should use `EXACT` but are set to `FUZZY` are the most common cause\ +of widespread false positives. Date of birth, SSN, national ID, and tax IDs should always use `EXACT`. Fuzzy tolerance on these fields allows records with different values to score above the match threshold. + +**Also check**: Are there any fields that should be `DONT_USE` contributing to the match?\ +scores? Internal IDs and sequence numbers that happen to appear similar across records can inflate match scores incorrectly. + +
+ +
+ +How to identify the cause + +Look at the false positive cluster in your output. Ask: + +Do the records share high values on some fields but clearly differ on fields that should be discriminating? For example, can you have the same first name and city but different dates of birth? + +This scenario is almost always a training data issue. The model has not seen enough non-match-labeled pairs that look similar in some fields but differ in the discriminating ones. It has learned that similarity in those shared fields is enough for a match because you have not shown it the counter-examples. + +**The fix**: Run `findTrainingData` again and find pairs that look like the false positive, similar on the misleading fields, different on the discriminating ones and label them as No Match. The similarity model will learn to use the discriminating fields correctly. + +
+ +A false positive is a cluster that contains records representing different real-world entities. They look similar enough that the model merged them, but they should not be in the same cluster. + +### Diagnosing false positives records that should not be together + +{% hint style="success" icon="right-long" %} +Before debugging the similarity model, confirm the missed pairs are actually reaching it. If two records are in different blocks, the similarity model never evaluates them regardless of its accuracy. +{% endhint %} + +How to inspect the similarity model, diagnose false positives and false negatives, and improve accuracy through targeted retraining.\ +\ +The similarity model scores every candidate pair that the blocking model passes through. If your results contain records incorrectly merged into the same cluster (false positives) or matching records that were missed (false negatives), the similarity model is where to investigate after first confirming that blocking is not the cause. + + + ### Using scores to decide what to do with clusters
diff --git a/docs/running-zingg/build-and-save-the-model.md b/docs/running-zingg/build-and-save-the-model.md index 48d741067..1929fe194 100644 --- a/docs/running-zingg/build-and-save-the-model.md +++ b/docs/running-zingg/build-and-save-the-model.md @@ -8,7 +8,15 @@ description: >- The training phase builds up the Zingg models using the training data from your label sessions and writes them to `zinggDir/modelId` as specified in your config. -Once saved, reuse the same `modelId` in all subsequent phases - `match`, `link`, and `runIncremental` to apply this trained model to your data. +
ModelPath
Blocking modelzinggDir/modelId/model/block/
Similarity modelzinggDir/modelId/model/zingg.block
Both modelsWritten during the train phase. Present before any match, link,
or incremental run can proceed.
+ +Once saved, reuse the same `modelId` in all subsequent phases - `match`, `link`, and `runIncremental` to apply the trained models to your data. + +If you retrain with a new model, use `Compare Model Results (diff phase)` in Enterprise to benchmark the new model against the current one before deploying. Zingg shows you exactly which clusters changed, merged, or split between the two models. + +{% hint style="success" icon="right-long" %} +**Read more**: +{% endhint %} {% hint style="success" icon="right-long" %} Model saved to: `zinggDir/modelId` @@ -50,7 +58,6 @@ zingg.initAndExecute() {% endtab %} {% tab title="Enterprise Snowflake" %} - ### Run train Zingg on Snowflake can be run either from a local terminal via the CLI, or natively inside Snowflake as a job service. @@ -80,9 +87,9 @@ USING (PHASE => 'train', CONFIG => ''); `` is the name of your Zingg configuration json file. -****Monitor the job** +\*\***Monitor the job** -Retrieve the service logs to monitor execution and debug any failures.** +Retrieve the service logs to monitor execution and debug any failures.\*\* ```sql SELECT SYSTEM$GET_SERVICE_LOGS('ZINGG_TRAIN_ASYNC_JOB_SERVICE', 0, 'zingg-async-job-container'); @@ -97,6 +104,5 @@ Once the job completes, stop the compute pool to release resources. ```sql ALTER COMPUTE POOL CONTAINER_ZINGG_POOL STOP ALL; ``` - {% endtab %} -{% endtabs %} \ No newline at end of file +{% endtabs %} diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md index c2fb14c27..2540e8b4c 100644 --- a/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/README.md @@ -12,29 +12,11 @@ When you run the train phase, Zingg builds two separate machine learning models Understanding what each model does gives you a clear framework for diagnosing problems, tuning performance, and knowing which part of the pipeline to adjust when results are not what\ you expect. -### +Models are saved within the customer environment at a location of their choice. - - -### - {% hint style="success" icon="right-long" %} -* Run [Incremental Matching](../../../running-zingg/run-incremental-matching.md) - how the graph grows over time -{% endhint %} - -### Where models are saved - -
ModelPath
Blocking modelzinggDir/modelId/model/block/
Similarity modelzinggDir/modelId/model/zingg.block
Both modelsWritten during the train phase. Present before any match, link,
or incremental run can proceed.
- -Use the same `modelId` across all subsequent phases - `match`, `link`, and `runIncremental` to apply both models to your full dataset. - -If you retrain with a new model, use `Compare Model Results (diff phase)` in Enterprise to benchmark the new model against the current one before deploying. Zingg shows you exactly which clusters changed, merged, or split between the two models. - -{% hint style="success" icon="right-long" %} -**Read more**: - * [Compare Model Results](../../../running-zingg/compare-model-results.md) - benchmark two models before deploying * [Reassign Zingg ID](../../../running-zingg/reassign-zingg-id.md) - carry existing IDs to a new model {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md index e0d4b38ac..6cba54eca 100644 --- a/docs/zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md @@ -24,69 +24,3 @@ For diagnosing similarity model behaviour and concept details → [Similarity Mo -How to inspect the similarity model, diagnose false positives and false negatives, and improve accuracy through targeted retraining.\ -\ -The similarity model scores every candidate pair that the blocking model passes through. If your results contain records incorrectly merged into the same cluster (false positives) or matching records that were missed (false negatives), the similarity model is where to investigate after first confirming that blocking is not the cause. - -{% hint style="success" icon="right-long" %} -Before debugging the similarity model, confirm the missed pairs are actually reaching it. If two records are in different blocks, the similarity model never evaluates them regardless of its accuracy. -{% endhint %} - -### Diagnosing false positives records that should not be together - -A false positive is a cluster that contains records representing different real-world entities. They look similar enough that the model merged them, but they should not be in the same cluster. - -
- -How to identify the cause - -Look at the false positive cluster in your output. Ask: - -Do the records share high values on some fields but clearly differ on fields that should be discriminating? For example, can you have the same first name and city but different dates of birth? - -This scenario is almost always a training data issue. The model has not seen enough non-match-labeled pairs that look similar in some fields but differ in the discriminating ones. It has learned that similarity in those shared fields is enough for a match because you have not shown it the counter-examples. - -**The fix**: Run `findTrainingData` again and find pairs that look like the false positive, similar on the misleading fields, different on the discriminating ones and label them as No Match. The similarity model will learn to use the discriminating fields correctly. - -
- -
- -If false positives are widespread, not isolated - -If false positives affect many clusters rather than a specific pattern, check your field match types before adding more training data. - -Fields that should use `EXACT` but are set to `FUZZY` are the most common cause\ -of widespread false positives. Date of birth, SSN, national ID, and tax IDs should always use `EXACT`. Fuzzy tolerance on these fields allows records with different values to score above the match threshold. - -**Also check**: Are there any fields that should be `DONT_USE` contributing to the match?\ -scores? Internal IDs and sequence numbers that happen to appear similar across records can inflate match scores incorrectly. - -
- -### Diagnosing false negatives matches that were missed - -A false negative is a pair of records that represent the same entity but were not placed in the same cluster. The similarity model scored them below the match threshold or the blocking model never allowed them to be compared. - -#### Confirm it as a similarity problem, not a blocking problem - -Run `verifyBlocking`. If the missed pair is not being blocked together, the similarity model is not the cause. Address the blocking issue first by referring to the [Blocking Model](blocking-model.md) page. - -If `verifyBlocking` confirms the pair is reaching the similarity model but still not matching, proceed to the training data fix below. - -#### Add match labels for the missed pattern - -Run `findTrainingData` and look for pairs similar to the ones being missed. Label them as `match`. The similarity model needs to see this pattern in the training data to learn it. If the variation that\ -causes the mismatch like a specific abbreviation pattern, a missing field, or a transliteration not represented in your labeled pairs; the model has no basis for scoring it above the threshold. - -A small number of targeted labels for the specific pattern being missed is more effective than a large general labeling run. - -{% hint style="success" icon="right-long" %} -Ordering clusters by `avg_min` ascending puts the weakest clusters at the top, those are the ones worth reviewing for false positives first. - -**Read more**: - -* [Interpret Output Scores](../../../interpreting-results/interpret-output-scores.md) - `Z_MINSCORE` and `Z_MAXSCORE` explained -* [Label Training Pairs](../../../running-zingg/label-training-pairs.md) - adding training data for missed patterns -* [Verify blocking](../../../running-zingg/verify-blocking.md) -{% endhint %} diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index 34269be57..4a339b6cf 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -6,16 +6,6 @@ description: And why they are harder than it looks Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem. And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. -| Problem | Description | -| -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **The N² comparison problem** |

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

| -| **The variation problem - why rules always break** | Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance. | -| **The confidence problem - why binary match/no-match is not enough** |

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

| -| **The pairs are not enough problem** |

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

| -| **The incremental new, updated and deleted data problem** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

| -| **The data drift problem - why a model trained once decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

| -| **The governance gap** | Explaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching. | -| **The domain experience gap** |

Some records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)

| -| | | +
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always breakEntities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.
The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

The pairs are not enough problem

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

The incremental new, updated and deleted data problem

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

The data drift problem - why a model trained once decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

The governance gapExplaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching.
The domain experience gapSome records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)

From 9ee34204b6025a018e54415710050c6091ec2634 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 05:45:51 +0000 Subject: [PATCH 106/144] GITBOOK-383: No subject --- docs/README.md | 2 +- docs/SUMMARY.md | 7 ++++--- docs/{zingg-concepts => }/entity-resolution/README.md | 8 ++++---- .../entity-resolution/rule-based-entity-resolution.md | 0 docs/running-zingg/quick-start-docker.md | 2 +- docs/running-zingg/step-by-step-guide.md | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) rename docs/{zingg-concepts => }/entity-resolution/README.md (96%) rename docs/{zingg-concepts => }/entity-resolution/rule-based-entity-resolution.md (100%) diff --git a/docs/README.md b/docs/README.md index 0d1cbeadd..f23dd8737 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,6 +16,6 @@ Whether you're resolving customers, patients, suppliers, citizens, or product is ### What do you want to do today? -
What is entity resolutionentity-resolutionUnderstand the problem Zingg solves, why fragmented entity data breaks downstream systems, and why rule-based approaches fail at scale.
Run Zingg for the first timestep-by-step-guide.mdGet Zingg running on your platform and see your first match result in under 30 minutes.
Connect your datapipes-and-data-connections.mdConfigure Zingg pipes for your data source - cloud warehouses, cloud storage, databases, and file formats.
Build and train a modelbuild-and-save-the-model.mdConfigure your data, label training pairs, and build a model for your dataset.
Interpret and tune resultsconfigure-field-standardization.mdUnderstand your match output, set thresholds, and improve accuracy.
Referencesconfiguration-schema.mdConfiguration schema, CLI commands, runtime properties, and FAQ.
+
What is entity resolutionentity-resolutionUnderstand the problem Zingg solves, why fragmented entity data breaks downstream systems, and why rule-based approaches fail at scale.
Run Zingg for the first timestep-by-step-guide.mdGet Zingg running on your platform and see your first match result in under 30 minutes.
Connect your datapipes-and-data-connections.mdConfigure Zingg pipes for your data source - cloud warehouses, cloud storage, databases, and file formats.
Build and train a modelbuild-and-save-the-model.mdConfigure your data, label training pairs, and build a model for your dataset.
Interpret and tune resultsconfigure-field-standardization.mdUnderstand your match output, set thresholds, and improve accuracy.
Referencesconfiguration-schema.mdConfiguration schema, CLI commands, runtime properties, and FAQ.
{% embed url="https://www.zingg.ai/company/contact/contact" %} diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 3e07feebc..9f08815f5 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,10 +1,11 @@ # Table of contents +* [What is Zingg](README.md) +* [Entity Resolution](entity-resolution/README.md) + * [Rule based entity resolution](entity-resolution/rule-based-entity-resolution.md) + ## Zingg Concepts -* [What is Zingg](README.md) -* [Entity Resolution](zingg-concepts/entity-resolution/README.md) - * [Rule based entity resolution](zingg-concepts/entity-resolution/rule-based-entity-resolution.md) * [Zingg Entity Resolution Platform](zingg-concepts/zingg-entity-resolution-platform/README.md) * [The problems Zingg is built to solve](zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md) * [Deterministic vs Probabilistic Matching](zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) diff --git a/docs/zingg-concepts/entity-resolution/README.md b/docs/entity-resolution/README.md similarity index 96% rename from docs/zingg-concepts/entity-resolution/README.md rename to docs/entity-resolution/README.md index a64d5bcb2..8f1ec0217 100644 --- a/docs/zingg-concepts/entity-resolution/README.md +++ b/docs/entity-resolution/README.md @@ -84,7 +84,7 @@ together, and the LLM reasons over a complete, entity-aware view. This process is the Identity RAG pattern entity resolution as infrastructure for AI accuracy. {% hint style="success" icon="right-long" %} -**Read more**: [Enhancing LLM applications with Zingg and LangChain](../../recipes-and-integration/identity-rag-with-zingg-and-langchain.md) +**Read more**: [Enhancing LLM applications with Zingg and LangChain](../recipes-and-integration/identity-rag-with-zingg-and-langchain.md) {% endhint %}
@@ -141,7 +141,7 @@ Not if your data has fuzzy variation. Graph databases like Neo4j are excellent a Zingg and graph databases work best together. Zingg does entity resolution, which means it finds out which records are for the same entity using ML-based probabilistic and deterministic matching. The graph database is used for downstream relationship analysis and inference in AML, KYC, Knowledge Graph, and Customer 360 scenarios. {% hint style="success" icon="right-long" %} -**Read more**: [Connect graph databases](../../connect-your-data/connect-graph-databases-neo4j.md) +**Read more**: [Connect graph databases](../connect-your-data/connect-graph-databases-neo4j.md) {% endhint %} @@ -149,7 +149,7 @@ Zingg and graph databases work best together. Zingg does entity resolution, whic {% hint style="success" icon="right-long" %} Ready to see entity resolution in action? -* [Run Zingg on your local machine](../../running-zingg/quick-start-docker.md) - full workflow in 30 minutes +* [Run Zingg on your local machine](../running-zingg/quick-start-docker.md) - full workflow in 30 minutes {% endhint %} {% hint style="warning" icon="right-long" %} @@ -157,5 +157,5 @@ Need entity resolution at production scale with persistent identity and incremen processing? * [Talk to us about Enterprise](https://www.zingg.ai/company/contact/contact) -* [Community vs Enterprise](../../community-and-enterprise-editions/community-vs-enterprise/)
+* [Community vs Enterprise](../community-and-enterprise-editions/community-vs-enterprise/)
{% endhint %} diff --git a/docs/zingg-concepts/entity-resolution/rule-based-entity-resolution.md b/docs/entity-resolution/rule-based-entity-resolution.md similarity index 100% rename from docs/zingg-concepts/entity-resolution/rule-based-entity-resolution.md rename to docs/entity-resolution/rule-based-entity-resolution.md diff --git a/docs/running-zingg/quick-start-docker.md b/docs/running-zingg/quick-start-docker.md index 8589d4138..5acacc194 100644 --- a/docs/running-zingg/quick-start-docker.md +++ b/docs/running-zingg/quick-start-docker.md @@ -10,7 +10,7 @@ description: >- This page walks you through the full Zingg workflow on your local machine using Docker. You will install Zingg, connect sample data, find candidate pairs, label those pairs, train the model, and run match to see your first results. By the end you will have run every phase of the Zingg workflow and seen entity resolution working on your local machine with real data. {% hint style="success" icon="right-long" %} -New to entity resolution? Read [Entity Resolution](../zingg-concepts/entity-resolution/) for the problem space and why Zingg's approach works. +New to entity resolution? Read [Entity Resolution](../entity-resolution/) for the problem space and why Zingg's approach works. {% endhint %} {% tabs %} diff --git a/docs/running-zingg/step-by-step-guide.md b/docs/running-zingg/step-by-step-guide.md index 4bb375e65..962d42ea6 100644 --- a/docs/running-zingg/step-by-step-guide.md +++ b/docs/running-zingg/step-by-step-guide.md @@ -14,7 +14,7 @@ The phases below represent the complete workflow from first setup to production This page describes the workflow you follow once Zingg is installed.\ For installing Zingg on your platform → [Install Zingg](install-zingg.md). -New to entity resolution or want to understand the problem space before diving in? → E[ntity Resolution](../zingg-concepts/entity-resolution/) +New to entity resolution or want to understand the problem space before diving in? → E[ntity Resolution](../entity-resolution/) {% endhint %} {% stepper %} From 01f9725e342325e703b4886761463337b3d1bc23 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 05:56:47 +0000 Subject: [PATCH 107/144] GITBOOK-384: No subject --- docs/SUMMARY.md | 1 + .../set-deterministic-matching-conditions.md | 42 +++++++++++++++++++ ...deterministic-vs-probabilistic-matching.md | 17 +------- 3 files changed, 45 insertions(+), 15 deletions(-) create mode 100644 docs/running-zingg/set-deterministic-matching-conditions.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 9f08815f5..aa9c318da 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -66,6 +66,7 @@ * [Cluster Approval](running-zingg/cluster-approval.md) * [Compare Model Results](running-zingg/compare-model-results.md) * [Knowledge Graph](running-zingg/knowledge-graph.md) +* [Set Deterministic Matching Conditions](running-zingg/set-deterministic-matching-conditions.md) *** diff --git a/docs/running-zingg/set-deterministic-matching-conditions.md b/docs/running-zingg/set-deterministic-matching-conditions.md new file mode 100644 index 000000000..0e77644dd --- /dev/null +++ b/docs/running-zingg/set-deterministic-matching-conditions.md @@ -0,0 +1,42 @@ +--- +tags: + - enterprise-only +--- + +# Set Deterministic Matching Conditions + +{% hint style="info" icon="right-long" %} +Deterministic matching is Enterprise only. Available in Enterprise Lite and above +{% endhint %} + +To configure deterministic matching, define each rule as a `DeterministicMatching` condition in Python or as a `matchCondition` block in JSON, then pass all conditions to `setDeterministicMatchingCondition()`. Full Python and JSON examples → [Configure Zingg](configure-zingg.md) + +### How the conditions work + +Each `matchCondition` is evaluated independently. Any pair that satisfies at least one condition is resolved as a match with a score of 1. You can define as many conditions as your data requires. + +Using the example above: + +
ConditionWhat it means
fname + stNo + add1 match exactlySame first name at the same street number and address line → treated as same entity, score 1.
fname + dob + ssn match exactlySame first name, date of birth, and SSN → treated as same entity, score 1.
fname + email match exactlySame first name and email address → treated as same entity, score 1.
+ +
+ +When should I use deterministic matching? + +Use deterministic matching when your data contains reliable unique identifier fields where an exact match between two records should always mean they are the same entity. + +Good candidates for deterministic rules: + +* First name + email address +* First name + date of birth + national ID +* First name + street number + address line 1 + +Do not use deterministic matching on fields that are frequently incomplete or inconsistent. Null values or formatting variations will cause valid matches to be missed. Apply deterministic rules only to fields you trust. + +If you are unsure, start with probabilistic matching only. Add deterministic rules once you have reviewed match output and identified high-confidence identifier combinations in your data. + +{% hint style="success" icon="right-long" %} +**Read more:** [Configure Zingg](configure-zingg.md) | [Concepts glossary](../frequently-asked-questions/concept-glossary.md) +{% endhint %} + +
diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md b/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md index 94526699d..9c76b3a54 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md @@ -28,21 +28,7 @@ Zingg applies deterministic rules first. If two records share the same combinati Probabilistic and deterministic matching in Zingg Enterprise runs both in a single flow, so you do not have to choose between them. -### Configuring deterministic matching - TODO shift to config - -{% hint style="info" icon="right-long" %} -Deterministic matching is Enterprise only. Available in Enterprise Lite and above -{% endhint %} - -To configure deterministic matching, define each rule as a `DeterministicMatching` condition in Python or as a `matchCondition` block in JSON, then pass all conditions to `setDeterministicMatchingCondition()`. Full Python and JSON examples → [Configure Zingg](../../running-zingg/configure-zingg.md) - -### How the conditions work - -Each `matchCondition` is evaluated independently. Any pair that satisfies at least one condition is resolved as a match with a score of 1. You can define as many conditions as your data requires. - -Using the example above: - -
ConditionWhat it means
fname + stNo + add1 match exactlySame first name at the same street number and address line → treated as same entity, score 1.
fname + dob + ssn match exactlySame first name, date of birth, and SSN → treated as same entity, score 1.
fname + email match exactlySame first name and email address → treated as same entity, score 1.
+###
@@ -65,3 +51,4 @@ If you are unsure, start with probabilistic matching only. Add deterministic rul {% endhint %}
+ From e5f5c33ac51938f884376cc40efb5cf303abcd85 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 06:57:12 +0000 Subject: [PATCH 108/144] GITBOOK-385: No subject --- docs/SUMMARY.md | 2 +- ...deterministic-vs-probabilistic-matching.md | 22 ------------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index aa9c318da..a4589c7c3 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -51,6 +51,7 @@ * [Quick Start (Docker)](running-zingg/quick-start-docker.md) * [Install Zingg](running-zingg/install-zingg.md) * [Configure Zingg](running-zingg/configure-zingg.md) +* [Set Deterministic Matching Conditions](running-zingg/set-deterministic-matching-conditions.md) * [Pass Through](running-zingg/pass-through.md) * [Create Training Data](running-zingg/create-training-data.md) * [Label Training Pairs](running-zingg/label-training-pairs.md) @@ -66,7 +67,6 @@ * [Cluster Approval](running-zingg/cluster-approval.md) * [Compare Model Results](running-zingg/compare-model-results.md) * [Knowledge Graph](running-zingg/knowledge-graph.md) -* [Set Deterministic Matching Conditions](running-zingg/set-deterministic-matching-conditions.md) *** diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md b/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md index 9c76b3a54..d7ec4ff4d 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md @@ -28,27 +28,5 @@ Zingg applies deterministic rules first. If two records share the same combinati Probabilistic and deterministic matching in Zingg Enterprise runs both in a single flow, so you do not have to choose between them. -### -
- -When should I use deterministic matching? - -Use deterministic matching when your data contains reliable unique identifier fields where an exact match between two records should always mean they are the same entity. - -Good candidates for deterministic rules: - -* First name + email address -* First name + date of birth + national ID -* First name + street number + address line 1 - -Do not use deterministic matching on fields that are frequently incomplete or inconsistent. Null values or formatting variations will cause valid matches to be missed. Apply deterministic rules only to fields you trust. - -If you are unsure, start with probabilistic matching only. Add deterministic rules once you have reviewed match output and identified high-confidence identifier combinations in your data. - -{% hint style="success" icon="right-long" %} -**Read more:** [Configure Zingg](../../running-zingg/configure-zingg.md) | [Concepts glossary](../../frequently-asked-questions/concept-glossary.md) -{% endhint %} - -
From 496ad420bb2b1e18c4a7d4e632b0130eb7637d3f Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 07:02:15 +0000 Subject: [PATCH 109/144] GITBOOK-386: No subject --- docs/zingg-concepts/platform-infrastructure-vs-data-platform.md | 2 +- docs/zingg-concepts/zingg-phases.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md b/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md index 8628dc438..11d0b1c72 100644 --- a/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md +++ b/docs/zingg-concepts/platform-infrastructure-vs-data-platform.md @@ -4,7 +4,7 @@ description: Where Zingg runs, which data it resolves # Platform Infrastructure vs Data Platform -Two things that are easy to conflate but mean different things in Zingg's context. Platform infrastructure is where computation happens, such as the Databricks/Fabric/EMR/Glue/Dataproc Spark service or Snowflake compute that processes your data. A data platform is where your data lives - Snowflake tables, Databricks Lakehouse and Unity Catalog, OneLake, BigQuery, Redshift, or a file store like S3. +Two things that are easy to conflate but mean different things in Zingg's context. Platform infrastructure is where computation happens, such as the Databricks/Fabric/EMR/Glue/Dataproc/Docker Spark service or Snowflake compute that processes your data. A data platform is where your data lives - Snowflake tables, Databricks Lakehouse and Unity Catalog, OneLake, BigQuery, Redshift, or a file store like S3. These are independent: you can have your data in Snowflake but run Zingg's computation on Spark. Zingg connects to the data platform and runs computations on whichever engine you configure. diff --git a/docs/zingg-concepts/zingg-phases.md b/docs/zingg-concepts/zingg-phases.md index 7abc49713..1ecc1cb69 100644 --- a/docs/zingg-concepts/zingg-phases.md +++ b/docs/zingg-concepts/zingg-phases.md @@ -4,7 +4,7 @@ description: Steps in the entity resolution pipeline # Zingg Phases -Zingg programs run different aspects of the entity resolution pipeline. Each aspect covers one aspect of the pipeline. Here are the phases Zingg defines. +Zingg programs run different aspects of the entity resolution pipeline. Each phase covers one aspect of the pipeline. Here are the phases Zingg defines.
From 22ce183374db3af007e09005c64e187f4bb93abf Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 07:03:42 +0000 Subject: [PATCH 110/144] GITBOOK-387: No subject --- docs/frequently-asked-questions/frequently-asked-questions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/frequently-asked-questions/frequently-asked-questions.md b/docs/frequently-asked-questions/frequently-asked-questions.md index bc47167b9..115b0c5ab 100644 --- a/docs/frequently-asked-questions/frequently-asked-questions.md +++ b/docs/frequently-asked-questions/frequently-asked-questions.md @@ -82,8 +82,8 @@ Zingg is tested primarily on Linux and macOS. On Windows, use the Zingg Docker i How do I upgrade Zingg? -* For the Python package: `pip install --upgrade zingg`. -* For the Docker image: `docker pull zingg/zingg:` - replace `` with the release tag, for example `zingg/zingg:0.5.0`. Check `github.com/zinggAI/zingg/releases` for the latest version. +* For the Python package: `pip install --upgrade zingg`. Also update the new jar from the release. +* For the Docker image: `docker pull zingg/zingg:` - replace `` with the release tag, for example `zingg/zingg:0.7.0`. Check `github.com/zinggAI/zingg/releases` for the latest version. After upgrading, your existing trained models remain compatible. You do not need to retrain unless you want to use new match types or features that are introduced in the new version. From 00e62459401c26a60af037dc23849c16cef35c6c Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Wed, 19 Aug 2026 12:48:03 +0530 Subject: [PATCH 111/144] Docs: update explain phase and examples Rename explainOutput -> explain in docs and update examples. Python examples now use ExplainArguments with setParentArgs and a configured explainOutput pipe; Zingg ID is passed via EClientOptions (phase "explain"). CLI examples updated to --phase explain and show CSV usage. Added Enterprise Snowflake section and clarified that explain covers probabilistic matches only. --- .../explain-a-specific-cluster.md | 84 ++++++++++++++----- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/docs/interpreting-results/explain-a-specific-cluster.md b/docs/interpreting-results/explain-a-specific-cluster.md index ca669c3e8..0681bdefd 100644 --- a/docs/interpreting-results/explain-a-specific-cluster.md +++ b/docs/interpreting-results/explain-a-specific-cluster.md @@ -1,6 +1,6 @@ --- description: >- - Run the explainOutput phase for a specific Zingg ID to see exactly how that + Run the explain phase for a specific Zingg ID to see exactly how that cluster was formed. tags: - ent @@ -14,7 +14,7 @@ tags: Enterprise only. Requires a completed match or `runIncremental` phase before running. {% endhint %} -The `explainOutput` phase takes a Zingg ID as input and returns the pair-level evidence for how\ +The `explain` phase takes a Zingg ID as input and returns the pair-level evidence for how\ that cluster formed. You can see which record pairs were compared, what their similarity scores were, and how transitive matching connected records through intermediate pairs. {% tabs %} @@ -32,55 +32,70 @@ from zinggEC.enterprise.common.EClientOptions import * from zinggES.enterprise.spark.ESparkClient import * ``` -### Step 2: Set up `ExplainArgument` +### Step 2: Set up the base arguments -Use the same arguments setup as your Configure Zingg notebook. All field definitions, pipes, `modelId`, and `zinggDir` must match the original match run exactly. +Use the same arguments setup. All field definitions, pipes, `modelId`, and `zinggDir` must match the original match run exactly. ```python args = EArguments() args.setModelId("your-model-id") args.setZinggDir("/tmp/models") +# ... same field definitions and pipes as your original match run ``` -### **Step 3: Set the Zingg ID to explain** +### Step 3: Wrap the arguments and set where the explain output goes -Find Zingg IDs in the `ZINGG_ID` column of your match output. Replace the value below with the Zingg ID you want to explain. +`explain` needs a pipe telling it where to write the result. ```python -args.setZinggId("ea67d79a-56a7-4431-ab55-d08bb3c10e2e") +explainArgs = ExplainArguments() +explainArgs.setParentArgs(args) + +explainPipe = ECsvPipe("outputExplain", "/tmp/zinggOutput_explain") +explainPipe.setHeader("true") +explainArgs.setExplainOutput(explainPipe) ``` -### **Step 4: Run the explain phase** +### Step 4: Set the phase and the Zingg ID to explain + +Find Zingg IDs in the `ZINGG_ID` column of your match output. The Zingg ID is passed as a client option alongside the phase. ```python -options = ClientOptions([ClientOptions.PHASE, "explainOutput"]) -zingg = EZingg(args, options) +options = EClientOptions([EClientOptions.PHASE, "explain", EClientOptions.ZINGG_ID, "ea67d79a-56a7-4431-ab55-d08bb3c10e2e"]) +``` + +### Step 5: Run the explain phase + +```python +zingg = EZingg(explainArgs, options) zingg.initAndExecute() ``` -### **Step 5: Read the explain output** +### Step 6: Read the explain output + +Read from wherever you pointed `explainOutput` in Step 3 — a CSV path here, but it can be any pipe format (parquet, Snowflake table, etc.). ```python -explain_output = spark.read.parquet(f"{zinggDir}/{modelId}/explainOutput") +explain_output = spark.read.csv("/tmp/zinggOutput_explain", header=True) explain_output.show() ``` -The output shows pair-level evidence for how the cluster formed. Each row is a record pair with their similarity score. +The output shows pair-level evidence for how the cluster formed. Each row is a record pair. {% hint style="success" icon="right-long" %} -**Read more**: `explainOutput` covers probabilistic matches only. Clusters formed through deterministic matching rules may return empty or partial results. For context on deterministic matching - [Configure Zingg](../running-zingg/configure-zingg.md). +**Read more**: `explain` covers probabilistic matches only. Clusters formed through deterministic matching rules may return empty or partial results. For context on deterministic matching - [Configure Zingg](../running-zingg/configure-zingg.md). {% endhint %} ### Using the CLI instead of Python API -If you prefer the CLI, create an `explainConfig.json` and run with the `--zinggid` flag: +Create an `explainConfig.json` and run with the `--zinggid` flag: -#### **`explainConfig.json`** +#### `explainConfig.json` ```json { "config" : "path_to_original_matching_config/config.json", - "explainOutput" : [ { + "explainOutput" : { "name" : "outputExplain", "format" : "csv", "props" : { @@ -88,18 +103,47 @@ If you prefer the CLI, create an `explainConfig.json` and run with the `--zinggi "delimiter" : ",", "header" : true } - } ] + } } ``` #### CLI command ```bash -./scripts/zingg.sh --phase explainOutput --zinggid ea67d79a-56a7-4431-ab55-d08bb3c10e2e --conf ./examples/febrl/explainConfig.json +./scripts/zingg.sh --phase explain --zinggid ea67d79a-56a7-4431-ab55-d08bb3c10e2e --conf ./examples/febrl/explainConfig.json ``` {% endtab %} {% tab title="Enterprise Snowflake" %} -**CONTENT FOR THIS SECTION TO BE PROVIDED BY SONAL LATER** +{% hint style="info" icon="right-long" %} +Same requirements apply: a completed match or `runIncremental` phase, and results cover probabilistic matches only. +{% endhint %} + +Run `explain` on Snowflake the same way as other phases — via the local CLI script, pointing at a Snowflake-flavored `explainConfig.json` and your Snowflake connection properties. + +#### `explainConfig.json` + +```json +{ + "config" : "path_to_original_matching_config/configSnow.json", + "explainOutput" : { + "name" : "outputExplain", + "format" : "snowflake", + "props" : { + "table" : "EXPLAIN_CUSTOMERS" + } + } +} +``` + +#### CLI command + +```bash +./scripts/zingg.sh --phase explain --zinggid ea67d79a-56a7-4431-ab55-d08bb3c10e2e \ + --conf ./examples/febrl/explainConfig.json \ + --properties-file +``` + +The `explainOutput` result is written to the Snowflake table configured in `props.table` (`EXPLAIN_CUSTOMERS` above). {% endtab %} {% endtabs %} From 42aba768661864b18f4ba3ac27a6219afdd179a5 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 07:50:00 +0000 Subject: [PATCH 112/144] GITBOOK-388: No subject --- ...g-is-built-to-solve-and-why-they-are-harder-than-it-looks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index 4a339b6cf..6d133a8f5 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -6,6 +6,6 @@ description: And why they are harder than it looks Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem. And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. -
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always breakEntities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.
The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

The pairs are not enough problem

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

The incremental new, updated and deleted data problem

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

The data drift problem - why a model trained once decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

The governance gapExplaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching.
The domain experience gapSome records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)
+
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always breakEntities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.
The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

The pairs are not enough problem

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

Managing addition, updation and deletion of records

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

The data drift problem - why a model trained once decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

The governance gapExplaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching.
The domain experience gapSome records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)
The schema problem

Since matching is tied heavily to fields, most tools end up imposing restrictions on attributes which can be present in the input. Zingg takes a completely different approach. By learning directly from the data, Zingg works with any fields and their combinations, with no hard requirements for mandatory email, phone number or other fields.

Zingg's approach also powers any entity type like supplier, product etc, not just customer.

The data preparation problem

From c15628429a0b083caf6a58fac2f04239cae148d6 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 07:57:29 +0000 Subject: [PATCH 113/144] GITBOOK-389: No subject --- docs/SUMMARY.md | 2 +- .../frequently-asked-questions.md | 20 ++++++++++++++++++- ...e-and-why-they-are-harder-than-it-looks.md | 2 +- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index a4589c7c3..d1b4300ab 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -142,7 +142,7 @@ ## Frequently Asked Questions -* [Frequently Asked Questions](frequently-asked-questions/frequently-asked-questions.md) +* [❓ Frequently Asked Questions](frequently-asked-questions/frequently-asked-questions.md) * [Frequently Asked Questions](frequently-asked-questions/concept-glossary.md) ## Security and Privacy diff --git a/docs/frequently-asked-questions/frequently-asked-questions.md b/docs/frequently-asked-questions/frequently-asked-questions.md index 115b0c5ab..e8baf44ea 100644 --- a/docs/frequently-asked-questions/frequently-asked-questions.md +++ b/docs/frequently-asked-questions/frequently-asked-questions.md @@ -4,7 +4,25 @@ description: >- performance, and troubleshooting. --- -# Frequently Asked Questions +# ❓ Frequently Asked Questions + +### Data Preparation + +
+ +What kind of preprocessing and data cleaning do I need before running Zingg? + +Zingg does not mandate any preprocessing on the input data. Zingg automatically takes care of extra spaces, punctutation characters and cases so you dont have to. + +
+ +
+ +Can my data come from different tables? + +Yes, you can add as many source pipes as you want. Just make sure they have the same schema. + +
### Training and labeling diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index 6d133a8f5..16fb55192 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -6,6 +6,6 @@ description: And why they are harder than it looks Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem. And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. -
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always breakEntities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.
The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

The pairs are not enough problem

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

Managing addition, updation and deletion of records

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

The data drift problem - why a model trained once decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

The governance gapExplaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching.
The domain experience gapSome records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)
The schema problem

Since matching is tied heavily to fields, most tools end up imposing restrictions on attributes which can be present in the input. Zingg takes a completely different approach. By learning directly from the data, Zingg works with any fields and their combinations, with no hard requirements for mandatory email, phone number or other fields.

Zingg's approach also powers any entity type like supplier, product etc, not just customer.

The data preparation problem
+
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always breakEntities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.
The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

The pairs are not enough problem

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

Managing addition, updation and deletion of records

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

The data drift problem - why a model trained once decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

The governance gapExplaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching.
The domain experience gapSome records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)
The schema problem

Since matching is tied heavily to fields, most tools end up imposing restrictions on attributes which can be present in the input. Zingg takes a completely different approach. By learning directly from the data, Zingg works with any fields and their combinations, with no hard requirements for mandatory email, phone number or other fields.

Zingg's approach also powers any entity type like supplier, product etc, not just customer.


From 7c5bbda8659fea15ab15f0c01d0d72436d9f0e60 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 08:02:57 +0000 Subject: [PATCH 114/144] GITBOOK-390: No subject --- docs/reference/hardware-sizing-and-benchmarks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/hardware-sizing-and-benchmarks.md b/docs/reference/hardware-sizing-and-benchmarks.md index 144a61c50..afee7a973 100644 --- a/docs/reference/hardware-sizing-and-benchmarks.md +++ b/docs/reference/hardware-sizing-and-benchmarks.md @@ -18,7 +18,7 @@ Use the benchmarks below as starting points when sizing your Zingg deployment. Y Real-world Zingg runs across different dataset sizes and hardware: -
RecordsFieldsHardwareRuntime
120kfebrl test schema4 cores, 10 GB RAM, local Spark5 minutes
5m (North Carolina Voters)voter schema4 cores, 10 GB RAM, local Spark~4 hours
9m3 fields (first name, last name, email)AWS m5.24xlarge - 96 cores, 384 GB RAM45 minutes
80m8 to 10 fields1 driver (128 GB RAM, 32 cores) + 8 workers (224 GB RAM, 64 cores)Less than 2
+
RecordsReported ByFieldsHardwareRuntime
120kZingg Teamfebrl test schema4 cores, 10 GB RAM, local Spark5 minutes
5m (North Carolina Voters)Zingg Teamvoter schema4 cores, 10 GB RAM, local Spark~4 hours
9mZingg Team3 fields (first name, last name, email)AWS m5.24xlarge - 96 cores, 384 GB RAM45 minutes
80mCommunity User8 to 10 fields1 driver (128 GB RAM, 32 cores) + 8 workers (224 GB RAM, 64 cores)Less than 2 hours
### When to use Spark local mode @@ -35,7 +35,7 @@ The threshold to move from local mode to a cluster is roughly: For local development, proof of concept work, and validating the match workflow before sizing production hardware, the Zingg Docker image runs on any machine with Docker installed. No cluster setup required. {% hint style="success" icon="right-long" %} -**Read more**: +**Read more**: * [Quick Start](../running-zingg/quick-start-docker.md) - run Zingg locally via Docker * [Install Zingg](../running-zingg/install-zingg.md) - full installation options across platforms From f2ee5577d64ea4910ff4a63551f60fcc8cc68408 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 08:09:58 +0000 Subject: [PATCH 115/144] GITBOOK-391: No subject --- docs/zingg-concepts/zingg-phases.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/zingg-concepts/zingg-phases.md b/docs/zingg-concepts/zingg-phases.md index 1ecc1cb69..a8f03f7bc 100644 --- a/docs/zingg-concepts/zingg-phases.md +++ b/docs/zingg-concepts/zingg-phases.md @@ -85,3 +85,11 @@ Reports what percentage of your known matching pairs are being blocked together Analyses a specified column and returns a list of high-frequency words that should be treated as stopwords. Optional phase used before training when address or company name fields contain repeated noise words like 'St', 'Ave', 'Inc', 'Ltd'.
+ +
+ +transform (Enterprise) + +Post processes Zingg output to consume in downstream applications. Supports disctionary based value substitution + +
From 20b44dad2ad17fe3d839d9586386a894aa7a09f3 Mon Sep 17 00:00:00 2001 From: Aditya Pareek <119134401+LOGANBLUE1@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:18:19 +0530 Subject: [PATCH 116/144] added images in databricks and fabric platform guide (#1380) --- .../assets/databricks-data-preview.png | Bin 0 -> 119219 bytes .../assets/databricks-install-zingg-jar.png | Bin 0 -> 40235 bytes .../assets/databricks-match-output.png | Bin 0 -> 35395 bytes .../assets/fabric-custom-library-upload.png | Bin 0 -> 41110 bytes docs/.gitbook/assets/fabric-data-preview.png | Bin 0 -> 142078 bytes .../assets/fabric-generatedocs-output.png | Bin 0 -> 468222 bytes .../assets/fabric-import-notebook.png | Bin 0 -> 39438 bytes .../assets/fabric-labeling-widget.png | Bin 0 -> 37310 bytes .../assets/fabric-lakehouse-upload-files.png | Bin 0 -> 64740 bytes docs/.gitbook/assets/fabric-match-output.png | Bin 0 -> 123965 bytes .../assets/fabric-new-environment.png | Bin 0 -> 51531 bytes .../assets/fabric-new-item-lakehouse.png | Bin 0 -> 123035 bytes .../assets/fabric-new-standard-session.png | Bin 0 -> 12010 bytes .../assets/fabric-zingg-jar-extracted.png | Bin 0 -> 117251 bytes .../assets/fabric-zingg-release-assets.png | Bin 0 -> 62536 bytes .../platform-guide-for-azure-databricks.md | 6 +++- .../platform-guide-for-microsoft-fabric.md | 31 +++++++++++++++--- 17 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 docs/.gitbook/assets/databricks-data-preview.png create mode 100644 docs/.gitbook/assets/databricks-install-zingg-jar.png create mode 100644 docs/.gitbook/assets/databricks-match-output.png create mode 100644 docs/.gitbook/assets/fabric-custom-library-upload.png create mode 100644 docs/.gitbook/assets/fabric-data-preview.png create mode 100644 docs/.gitbook/assets/fabric-generatedocs-output.png create mode 100644 docs/.gitbook/assets/fabric-import-notebook.png create mode 100644 docs/.gitbook/assets/fabric-labeling-widget.png create mode 100644 docs/.gitbook/assets/fabric-lakehouse-upload-files.png create mode 100644 docs/.gitbook/assets/fabric-match-output.png create mode 100644 docs/.gitbook/assets/fabric-new-environment.png create mode 100644 docs/.gitbook/assets/fabric-new-item-lakehouse.png create mode 100644 docs/.gitbook/assets/fabric-new-standard-session.png create mode 100644 docs/.gitbook/assets/fabric-zingg-jar-extracted.png create mode 100644 docs/.gitbook/assets/fabric-zingg-release-assets.png diff --git a/docs/.gitbook/assets/databricks-data-preview.png b/docs/.gitbook/assets/databricks-data-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..3b7c17652ac383cbeaa135c44b95020433c61991 GIT binary patch literal 119219 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 z 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

GGSy^-@wAV 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 P1k6Yu;?oDr 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 literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/databricks-match-output.png b/docs/.gitbook/assets/databricks-match-output.png new file mode 100644 index 0000000000000000000000000000000000000000..67b240557e1cdd27d9f41b5a804371880965a265 GIT binary patch literal 35395 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 eGsMIO2h(m1ACl{N=cX9Hep+m~=<))`fd2AfR0 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%0DQ{ZiT&cFBUOXwiSBkkm0VCtc+ejtLv5*Bv9 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 literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/fabric-data-preview.png b/docs/.gitbook/assets/fabric-data-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..9558125dd0e573e4d6727f93dec0cbaf981ea8eb GIT binary patch literal 142078 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�!vasxUR48$0vCDey`-H@F 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 literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/fabric-generatedocs-output.png b/docs/.gitbook/assets/fabric-generatedocs-output.png new file mode 100644 index 0000000000000000000000000000000000000000..e9bf4d0913354d9e2b94bea812f81d90f58c2bc9 GIT binary patch literal 468222 zcmZ6y1yq||(>072hoHgT-Q8V^1d6*m#ofKQyA>ef`EYagn)n|g@*%wBH{!|0lpx7mDP5E zfI!0h=Lrdsl>>x;Acc^V6jS%iIq&l7B$CQ~_^AGQ*|6bq>F=mfACV9mT5AS?k<^Zm zk{m(N{Mu*kdz6VsB;(KjjsN?*=fXC}^!K=6eqmC2a?;00f_hl^NJ>UBgrEfMr0k80 zjRw~*Z|^@Yh5atO`dG(fc>ms~9x!e$e$ihX!OegFbIefc@v#2(@=2@ChUy8FXH)VG z9V&ig|Hf00`2St*_GsXt!XqLOf&+bpMn0{;F|y3Ob7Mb^h0{25s#hUo=Cj--gNrP#LzXI!^9=b?UCRy)456Pqg`wf_LPXmO8B8A~BsG`P# zf~IBE3X&q1j0=KZ*Y5d?=eiQb?pCYAVX0Z&BsAjzdiZc;Q@tf+@TmO zu@bfHI;+t8|3cS_8`b@YIcoFYSKIJhrj8RZhNU5#Rp#>{-+%82NN${{D6T zWQkD;FtN_PyYM zP%N{7lUCetG8%@Gp`v4VAR9nKo)gEsPb(S}{BrpM%i~yGcRuKX{)zsecnF9v_r1}0 zVAF%6q$grk?RlfF${sOCT%~4lwcCwgP1@=(r#uL2A+}(nBe2>T5*^;Q7DS3n%Csc9 zH}M7Z3rKMI9MJ-LYv6b)^Y?VVYk0M|K#!iLpIbZ%RkK2@8S_$+SH&YXH3~=_u<&qM zh`|$V{}5B6J^cL%&i#^{Hr&RSE~_y;^0#;-!goh!!9pzHydr-ZhAlEk zK8Ee4JN$)sZA|Fw)9>7^5$hUXitj3B3SIqZSHSO^p}vp7E9=lY+WSZdXxCxB$dyp z2`j{PA&vWfu1qnC=|r&s3Orv=gkG;{&~A<%at9_sqlpny#XVh-v7DM)&!i2wQ@hwB z53`V?W*>@l+;oR`^Pl(aKTB`ezY}~tX!Ktj`IhtbnGg!0sMmd`xy*Q^$juwm){WYZ zOxEC7;^FlBjBmd0h`R7?cY2+;xbMkszoEFx4r0 z3BSLYoR4btWs-x_3{XPZB%RxiXKwbDM6Gmcr*@0EaS>j2NCg7hHucOS{&w^zS225S z#E(y*)g_XK+p&<_9#+8zt?0A3nq``nZ6e6nHCDVW_6to<=+)L&7KJ0mVhzm&;(+kK z>&4Y{@qT;>XBcepbl;SefWS_-^yZ;pkmhi7^$u!U-^B{6P?6aYxY)rq7Z zkbZ^{b8|YorZBmD4fY{V=m6Q_1!;a;S=Buc5oh5#nfUqN$O`zjaO(E_Vi|b8rzx^6 z)m9lZXaTxYchxLhaG)ES(4>|%A`RnjOG=)Q zs*v!u3l%u>7daA3smdPtC_G!->jihy|ql%94Uq?V1Q2uO&NobLdPXi`~q2|$7)gDS)~uXN~N94`D1 zj#W-WD9W|wjVO!r3b~YPVkU|3#h8M3Qr_M#=;CCDl2jmhg!=Mp3uu%qjPe3<0$@Rb ztzs0u>uMybfVANu#HOkR8j@@XI!>ju+*q8@r^h2S|JaQ7m^)>@xSlMcYYztb9KMEl zT}Ph;0iMfZ`r_yxy>y-7l=3Sk_Gm-CXhKWqR+_Js2{R)@i&)$_Vr1KLY|P=MG*RYO zcpOGC7L_`dtV_}5^zHL@X=+VuzxLYPZ;Qyuy^Js?us!&g^J2fjLL?A%8_}O zD?s!Y0pyG@+Rbpv7~R7b9sJ!%kPZ?j z4HqV0Umj+c%rif0sMs^bg|k_mEvVDMP&*yVM{&a0!g8mTCY!+}Mp-2|t;@X(ivtgS z>$66rhAG(`Vz?a1xuf?)2xHHkat4a6#Wb=Rxo&zl>`FH&$lh5j9<^F-J%BFdM%`#tz^NCc{e5QE5Kn))Nz z=MJ~NL0HEyY*0b(-I`GL|A56|(iz)Ditj_Ll`c|`qjA47vKzC*!MbA{b8Dz5UO`ig z_TZA{Z>eZ>5(vM)M@1Z}ugJH+#)(c6TV8;LlObYH9sB4Bj;^JjSeM!ZVNCZZC}XwJ;i#Z)`4U~%4*w#eFSVJ(vXx6_ zEyBh&Hy%27x*smcjW`D4;~4kBqo3R)M?w?4Si{0k1y3s-xd#tN{Ty%S&f2@b;@!R> z!TdNwER1q>OdOVKjH5`ksS5MWqhipkHA}ir21ijrmT8o?U|!D=q;{5+)|Z#&lmCMD zs|P9az!3o*Lg?49URfAvfR9e{XKFfcZn+K|^r!)x+F^8AnrHk9Raq>#@1oq_Nv#bb z(@JqJC}+($Agj9$*6j{L(Us7-S0&euY!R?Z7%q>5Q=HCBHqwk#nU9x6NuwG9Y3}da zMw>U<;}u2Z!r6uSDOUS+vH^|e3O-(V4>GhKjAV7suo#{+n5GT+LL|obZJ{Q%7YBUg z|ArlLJk8>Cdwcsziz7*sPOPtRzW?&iN}-l%?$s8X{!gpoR+!P>KW~i>&g8}K(NMeg zv$gU1T&KYnLub)Kl9(9gjg);_C#R!M|9qBf$2bhUzak*zZ9ItdER&lYWtQkSLY-4e zicM?^wbZ5D6!BWL!_y-k^)%MB{oJHe0xCzBnk9SK{x28#bR}t$fF-U{rLo#IW{#u?CEAmC&OZ7n+#*}lhZv4vmT1Rh_ zH%JUJtCE!hf(n|Ixl&7^7pR`2oAbN+1&y#^_Q(9zR-UzP9FlM39QR+dC&D`Y4IKq6 zDHyR|&MkfWH?MVMwUXBQ^68xv`m%icMPH!u&ba3bN$OVRK~11*-(ai%B)(7c6czbY zQk{Y!AjC>oGAax)EEagO5;ahaHNWpZy*orC!aQCYxAYQ`zKUU-JySRmop82-V{y{Vn zkENV*aQ2M)j-LbBVk#*5P|N+?Kh4qqiaM=YI3(H79Y!sz*oXqFKdKoj)avNEx%psF zKS7JG91>AsdhjG?jvTPCCma?*y7exDV^SYDA4b#>k&QQS^FWyO;^^x$c6R(zlDGT! zhG(JoYxkK;OUd$xSzU^J{o^Zr8ajdPrnNOH3 z>v&-8==O62V?xB&h(SeK{}sVyYK}tZr1zFRVibr3oXT1)QveQWFa85%peXr4AIYao zyDmj{`@U9NN-rDI_htN$T7c0k2m?NB7T?(Z{5F*h@+muOMz5yH{I!*EC1yogdTLh1TG4Jxw z?47sjTtR{r44R#HlIWq9V8F9v(>2;)Xa|-xSBqq!4|gAEmZ=>3fqj zU269XV)_2V=iBh6rBLI@lzORO_=bE}Pw-!&v~uk7nM~5{MFhe{%S(S{5Oa8)Q1-<% zd;jvc5Lwpga4P5Z;IkHXU~3A{P1xf@PRGk`os3M9wjh3+XoAD1&cj4NZzb?EL%|gtxP;w=+^Aay#6fYF^76N9UaORc?}o}k_@c7`5WG^^Mi{#SOqW| z7#^n*^v8qoUp%7ohaBwIn2?teVKRK#BX4Y!w^`=WmxE4Bi&{62p!}`wv|JhD0XhYr z6HF*uCUO2MS9ZIcbphKYWdHkKoS5cHos|fvOYd{*X4HtBrp5?zD4|TMKWM$4%au(^ zc32<)%s3y8b-P$V`XfhX$g++?xw<&~Kb5WYiIVmBxVX}F<)fX?k`-}raeo@>>gwe5 zm{4 zk+JyGa`dqDD$Es4g?+7VKZ7BvsxWD&Xt9mdD^;9K{#}38& zS!J4ZBnV;^)mI-mk?3>~3F6{n3;Pp5%;`28iE%%cPm|kwFx7nK((j%59Lkb_Zpna- z8+ozv4LO*Y7*y4k%eQN-T%8hW=KLb2Mp}X)>rOaZMhkK4#5T=+fp8AB>O z@>6-jurh40i*_Teg%rImtY0s9F3nsuX&gn|@DiJ7jkAa4W>f6v_7s-p7BvnIZE;*) z{0Q1)wCwz1{3HM@x?K7GW&VXGZr2YCJ+5E%7(FH8o{|+BTC^P5D-d0H!0f-#|_tFZUOM zx4$Vm{}d+!oREXKprWMlj#nDld+|}MSjVMYc~5YYr%dBU%2U7u|LX{mhsx>iy^Nx? zruzzR=>(QpJ;|&@n=ofS0pk5j@<=m0nFU(_7iPyOps@v8)QqsK{nD;zyVI2~F_ad^uLRw?kR?okq1K8qJQ&&Q@v`H0 z&e}{0-(7j!Vgg1#tJ!g$W$VQ=No_&Z$!C{nYc(-ZnPu~-X&6KJlwlC43r z-RH8+sHk_*hT8*khZC8?e*BB_MB=#f1#^bMk~&SY2s+s~W!`(&O4U&ZYxPS>6NjG9 zCuRT8u8DM(vWA9anihN0`O*<>zdgFj?Q7bX2a=tX^q!R2Lc}K7yCz59FE)T+dSl|_X}#1OL?|o(1_tr|ONHLYosV|68R`fLmyn;sQ zD0TYKUM&Sj;Meh=RIh(7|HfcH^O+vnl`6z8;n4N!06GXp9`+ta@Y9)ckR&cYrr_RB zY`1qe?hI!trUkRcM@g4tvzkXGFugE6>C`Jco#GHm{rL19xu0hU;Q}Dn(<51 zYwFK_-)%?{=xDt(B{BcV+stv9m74UqTd$Qju9QAPJl}kT=ka@*uzR`i#$nd#4I_Ca zxKEpSU8fONT27;({qx6pcl$%6vg-oXq5BB`NKqulL;oEQ@$RTi8x6{}D^A6WxWj|! z?5Y)M)1cUK%{8q?=i<+J3TGR)fBsYj>&C+XN&W9oOkTV9U3g;^u}Ea-wQsP9sq_Sn ztx;YwBr-GK4kiD3>mVzJ^SN*M9_ zcvrFRKjd!vTem%Cw~&nZ^_fR&P9$~&?jiZAEhBE=sGy>Wm)~TAzY)ij$r#;vH_sVu@;KM`myc0otw2s~!&WaPQ zD=}n308;9l!IDzN??`C<6+&r2?$TwqS|4^5@bSW^RT~Hf&HF`#VTm5I(`k8GB9eIb zQ)~?;?buiup>h}g&0yM;p3~5A+xdKcv4#h#U5PJe$bYuV@^D#ZPIlbt8lxtv>4HN+ z>?Tw^_JY075j^#;*RY)qPm1!QX8Ke-09-u+<)p~0VGJz=OIoi(Ifh6WEJ)WB923)| zevJTG^#V_ub$9>gJAoAs(c539Gq8PWY~X!=+<$sK7Mbbog%}|T z5QIpT5Bqzz>@tL{dEFB5(sI7lmCaDy8m#Ppn=E)Y$rRWBfpdrk$zBT%n<+uiKe{$Sl5PaS`D^dl(prssM6?LEzVp>E*3 zA@6_ZXn43l7oqC;(9w37WxL{jB7^XAp!0S?4UZYC^`v9@Hf|b~JK+7U^C1@3B%P&X zZmW-{=keoaTI9p!TSa_{0i$uMGwgb()Q>=`2VxxcVcw@X1w8NHErRY($J(zwJ)^b; zevmKS*Hm%d%T%E*M`~+sMCl)QA};$g0ewV1@O(nJSb7a?KUeorlVpO^tp-;C8+UReGZ- z@WmiIy`LE>ANE3MzHdgbg$#7we0D6o+HmojGVF|BKjp&rI=25@vcyYYT+tEDWP5dR zdN)((@$9Ewff=4qvXdYKXXIS#)i$6wR#cQM{PiB(_$| zrN8j|&BPB4nYOd)CcBl|pr9Z~;oHP`bQnm($9`ON@#`pM-(V))=C5nkWjABW{z;!m zd=t?8eoF}c`6ZF#|9s-=;o&mD)S^?tpxkB5>-*6jcG;DW&7fhFyn**0$>={FAiAF&3r|tdsbud*EFyvDmxtd-9KywMk8>Ai z>c+{d-0>@rxTO%s@`5be4v$*^W2j0Z_EZd(ZolG&Gr}V)4=rWLz4{E}ogLZ{T{J&ZN)JtNexcq~VqdTuWuOqzfn^?!|?uV{;B$mzu zA_4d7Q_Y?%OeNl+f0l>O<%reeQm?vo4-wqte%Nq9L&9O)nHIP@D9v`*!|i#5?RmR! zTy3(&1rxTTG+l>6jS8nZIhxt+?U{vgwXx&74RD~V)NX*IqZ07AG<*JYQmNY#N2QSV zb^bFC8#njxL-&Ias=ztxwD5~vcz8H%`b3W3{i@*2ASMehm|cut@jju8j%NMbR7RT?P%2F z_l_lYpKAygpY{_6+ku$ax_~o{Y0nE=zO#NFr(+gJ%Ne!x1XI(_J0wk&m4@`aPA|QW zeS%Ew_zYe8y36z)v)u`UajTO4E{`hnwIqs@41R3=3gfU|h$p?cT(pgum1?5SH_ZIr z%MCFPM>I>YOZc%%w)GS?)3A5$()JQgz}FmrNCaXpDhkPA{X-l8)zkVy{7e8)!zB?@ zVQ%=nVtQiG9I7otEY;nRJi4*~EBvmLL~ih*(O@>@9LIZPWmX;Ar7SeYQqvPR!Tdp} za6Bmu2kCszh(pp9JWZv5r$#1W%EdpviHwHgG_z^TcQUAn4hJ>=!f2%T`+V z?+smswJn39;6r5tD*mHH0j@8;w>t5cG?BspZSw>?NY4%@yq#X!1w1EUEpN8&;&xlv z8?~dkamcWjMng(C6s>eIWPZrwrlYSyP>3gxX@px9?LjFT8Af6mAYg>lW`UHTi$eJ< zZQQNvZ@rd1kb(n+eC~gNjfk!+M?4@P;Q7ZB*qk!C?RB!EU#dO2qG6G6t}i`$vfa*> zRB5qz!IpV)c4oTx^Gmta#`?N~rY0@~_$Chx-wzFegoJ}Y30+-+3XQ7ovsPb^b3L5F z^b%ZQ{D+HXbK9FNSL<5~d_pBOva*5?1iQ`ljt;Nan-ObU+ajGRC}s@usEo+(k=|WO z%E~+ti@T7&xSlOoOiy3cX#cWEp9LPu^kDV3P3n0f&!D1mxEj%b@Jbc4$8KnHmD;)@ zDAI^OKb#*%79gqBu;ysnQ&s?$%Xq6xrxSPeKJMd5C}2byB8n7UQSkDyun@9;4U zcHFngx={{qctn=@BXQwJWA@4%g1m#x_R0+&Mj{l$>~^qRNMB4<^r@b~rgUO!(0{(K zY-DB?x@lU}SmlMls4F(}&2**N3G+@_5W)L-b$)itNZs-{d404z5cI*h)cO1+#ruV> zrD^sH{j9bP1?vgDC~r+;ph=)@r2>g!|+6P(YA40o4+|)!p5AoA22-*Y&URkmtJ# zZ{xLG@+p}W!R+$t#B4N9;wSv2Ky-LIbZOEOp<<0>m~hT|btn+&=ezh+s24$`93hgJ zzNl{+cL#CCd20s7XZMid_yy`)LZ^me>n>lk z5q$n#^bes1vqZu`u<_q52fQuI(mjFAKtVX)awbnm@b7}!E0{}K-s}x=;-hdoZcUbc z&i=7{X1Cd%s@-65^G}$|%Ok$O+??nSL;5FdwQRZ*uy~Cr{F7+yMY9~$PcZL8gFXGg zzyO4L#TRKA8OW737aH58%H7)u7O;Ol>Nk6!_gA%>0~P9~7I1u0s% zB3FfTGf3f4chi@Rx7rca3<`pV7QiFe7#&7=s583sl_yfu?%o4V3xA|e8oG1F%Btbv zeK)95C8{VOC~8baL`FfYv%E487me2duHJK6Z0cNo+_z-jS{}1EUqO8G_Y@LvXpOVv zwl{lP-x?=cehYlI?=i7@lRj_o0d6(NN`WJwIZb|lZz+>{L`1E$DFMnaK&(#g_?F+o z($>hm7Z=K@+MP!-BoaX`H&!uJNqQ+5);RpU~*8a-hfbM|_C6SPtx4RxN z(Zjv{TTy+GBO*YM&8rVEVl*wnmj3h!Z>uN2(C>wIG%(I)aK7a|rK{(i>c#&B;_%%d zul2>Z&(VFn1UXAzud$gA8ur?{o3%7{aO*su9UMS`VGA7s0*Mm7U zmt`5@7)YB%`l3&8z+Wc)+yn55ogAWa#EwL&2k?@E=6G|aDi zN!!`wOV|}lG={W7*wRMLg<9?O$;#EGGq_Fp!#a(&yP-R8P-^z)6h2S`cXy4!P{}qB z5JRuEnkC=54(Iv0YM$-{-ursv0k}B6BOr6z0S4H5Rk7-9mzk@(?sd9vQ){k!q0zt? zW*e?G9f@m=aHz)r3r{(j;|7$rWd!rO@$=ON{k$4B7Cl&*e{;tVm^_YN&A#(KIQf_A!N%x}lP8^d)&N6jfks6g_znzizG+@xI0j^|Hdwm!!Q$3h&r>t^ zygFa@eE5U8mb{G2r+ATfUQGipDh`Xuou3uedo4Y0Ow0NnMDc>R@W(kWsAW0Mh+u7J zS=(+n0oZ6vr*rsnd~e6}{QfS!E*idJBjGZe?e>Q?HZ`%iovO>1V2GjrT3wYZ0YQ=? zOB~JQqxBj`5J{tV=%zDj_ovXQ{XRP@>gdRBZsrCn6#sEdkF)XqgHbejqvq-Q!>5ZC zZ8yUNd>_G@?DlJn2G8~Lt$))TDI|064FBT8jX+(#UpAeu-oaGF35TyLXg0Vu_WtED z7(uIkgQUrGrnc9gw`RcV1wLC&iXX3M@;E|n=5D?|->-We_YVNx-UJ`7DHcSY*(`FL z;G*C6{E!U;FdyFMMO3etUFJSs8Tgg>a!b}dZX4nQg8M+97`EI14_h8A8x3*YphOpD zFIHW~L(qZ~(+5csI($4~o$nbr;^7*WXjK>32 z5(QP{N|4+C#isOXO{u&q8trw+Pz8@Dw!vys2Yr~lPVppP+y@;*o6gpR&(YzNA#9Gg z{QA-YBqrMZa@7`kQnB>)a62G|pt?Qj&frC7&8MC3aO3Xrltv_s_}MVW_1)`F-ZiK@ z;4l>iOl0Q=B=ml5N`0M_M_g*#XA0koIXOUtt(I0<S|_x=ewzx zxVX8kEn-X74#=_x-aRMVim_CMNxfPKCMGCsOE?8iC|Fusx~9|>9>M&1phucF@Cx|)Uw3`<7lZ;I|p1d=)82Rp@4@He(XbY2Ipx`Cnf2(6Nc|e{{%9- zRdz5&44>!HGeiP}kMjfkbp)@$***ZA2Dvu2P>}Z#fxV6i*-Ycfyy_ggpx|Kp%jF)E zV^7AcPHJbU1)to%tqqsN#NmVS<)A^i!DV9q$5RByU#1q(W4-R%A*xQH#)BAXg#_tf zthz_~KFXnG_CYT=4f$d=rH<63!a}mfC2+b}<^(AR;0h7jtCyD2TVm2(n4%t9iII~k zyjX9U2S3QGg$)%eb>yrQdu?Z)W9n3Nth6Yfm^aN`UdeWU@_%~FFJ zOc+QV+hgL!H0dHSEGbu4*RP9TG)n-;OFG3VF|n}#7%_U*BWFIFCj6aSe~+uJsV|!P zL-!Z!Z3jsTTHU^UMMXulCGf$4(nX1vxI?RrV(2P0%K-7>V|6+yU^qA+HZGN8dVRdV z`QA6=n;%VP`tCq~KofCUNdn*{blFDiTRvh&i2ZUDw%$bWDl}9;CW%1IHWz#t_(q0S z8W-Tas1Y5`#OtkQ&z`{c6Y5p_0mf3LUKAdYn8NavtXxW6hBq_=BYiqWeOgYK^g=IJ z`T%}NB)J)|_c!Uo$Ic6~!-_0gus6kb%YQO~58vPCB|$;xG29uWDk+d|YBHga^~S$I zWW~J;Ha|!y#Yt1LWo(hPYFoV$`!~1X&4H>G4yRoL#dF(@%hEW z`COY$*&R~{;UG%^sw%+!emiv*FZqOwt} zI3Z$M3F3uS!-zrb({;9?z4-gp0=vUbSAJ)Yx9L5TV>G#g|Kt)jO*-v)$!@Z~LLrj0 z@S$G;xblTfVu-j!7g5Tiz>_z*%Hp!+-HW6^7gvde6+N^|fheC~ zgpo&*l;5BI!HWuSXxuZg8s2+b8-eoVz*N09qqTs@9=cy^xtD+V`o=a z$6o%aOZz`t+casVU?CovAAU63z7GX8f2HWI{(n4jLs_URba^_&_XYhuQjsmoyr9<2~Sc+qCi)0;5AQoJ6 z^b=GnI-U}P<-2yjBc=!#HhFJy0lp;4Q}50z6{IHF_+kzLqd5VKu0e~NJ>sH8knlDR z?{f zb59{IE-pGBgszN_?=Kd9eo+TrKl5~E?rU_1*3H7n4%{Bf)Or8?>1p@q3HL8w$KpNM z`L6ALCPvta8_?`%4qQ$WFlnwE9J9-g|K4$*?d*N)bqiK?F?~(Nt{ZlQ zz8N^E(;|j}Y|}&iir2A98*|PllCqpkgp~85NhejMF`#oHDFg#T3>G!6DxGE3LXtWp2|j1AlqG$>g0&xKkHO}cG=mOhM@SSb3La5hIG35GOXt)S+tmy} z4uvI7tDb{tTkMh`P%=|>b-sZQVQr-^pRBoMOAOuti^19A{#{BM@;g;`$mV8&cdJ#l zQgck2Rq8k4c?bSuandl-{F$;n8;w`xc%wZRKz>Bp-+5=zs73B~YgTmvxLb2_BGnq6 z(Xe0;EqXg-tL;EoN8}L~c8ielRJ;J!>KVnV61*7j2tLKESjSY70SX&q(bmd-6qm9M zg1+brJ~cT!)VQ(iqmLlMzx%3TmfsQgg;S%Lmv7q?u}3trPb6bYzU*iFMn|ogyZ79T zII5=T2rD-Wwmx5X_{{s#=%-lh?FdX#nd9W?7#vk=Kd?KF=@*z`Ae z6I|iM$IH*O@Rcl`U;&hb#}jQG zxxiFqCi2M%$%#=LFa5>D;WE$u$=VQwdRpzXKzMYT6}ejC6rMZ<#wS;HK{_ev56+nR zJsre3DpW-}^K;iJs{77cJ2fUaAxB@Jw8y%^q z#qRi1tL~_h>q0m&D6-E+p!oSr&O>E*`}E|+$Z(A=fKmv}fzfNCfuJddUS6k7*?kkE zQQk1!YIdTTY!^P|xI%*((5IBZ`WSGF243tK1`1MD(#OC!R2-g$`2#hi`(NFa4{_A@ z{EMg~E`yJZABg=h3MZ8I%H2a|GxwHXGL}p;U0JNYaHCc)5PCb-Cpl_x_!&T5-5Gaw z+>1<*EepmvBxN5a7c;!$2-qLG{GoG$APA8iqQq4-Hs|2bGU1R(+JDPTR+QB6$=53$ zN8F3L&SHY3f++68_t493qzIcZo_RkeS)H71&O|Cnh@ox{ z;bf`34Tqc#0SJk~v^m!z9oRV7z3!TZ1xIt6)oKU4!3#b|1bRr%-B90=@5So{^-l`H z4cCO8Gd6C)X0=9Sr!Eyy)zr7}WUX5qGx8mZ44_@fL*x6h_I=~LF@~CIFJY!}$6~ke$z7&I&CO7N|m@@$%8&dtgDuT_!QgW6I zL32x?Lo@uJLcJLhl$nxZDtVmbA2K}?ec4v_Rco6Ex{XU{wwpE99B$k2X6>39n001E zoype0RaF)!rBUJg?Z5yQ-KIob#hs}m&wk0i2Q+^{>QfG!Ah9A7fSl!Y%=r0nXn7^| zRW~}QAeP!IbX!&>s+BSqIpZQIiUt58vAYA}nQhdR29{+I((qffMEb?hA^?LD@oi!H zX;V10OifXAxo~?R8Km%o0$qpBa#U!>?C|1szsFkZd*AR)#7W8HlAS#l%aw3sN@~RD z*5Y|IOf^!O&3&So!*gw~$(EpLroM}ub@0VJ->fFV`8E7^OD7r!BUCbTrJgw7Jz9F) zOka`sbk?Y~X+b`q>>qo-%t0~eZv*p05EdLE^ancwL$z)&Ky#@-7hdlP4pe&1> zu#zXz5K_EsDC@dmlajA~+)gl((Y+Vd$!6T4SXP|k)W-Nz*@R;n{hT$i2P3uSyrp#f zG{Ik8#5?=X{-X^)Lqb)Tmo#3WJ&PHnh73W$o}axyDO(vtb<}#qagMoJ>ttT2zVO*5 z9D$La&*A3)oDmF0Y+SyTCi|+)2vbx{akf?Pe!N#gS?d12b#iJnC6MS$-JzBxqZdgr z>&}Myr!(FDol!novK*)Ov2CSyaNQ-ql?ox|vwjJ+kZRHq!E%AmSHnT&pGe5e6I>TI z=MGI#qn0{la&Z%OpK}xIGt>3XeBrPg}`3WuA85@nz$n02hRl{UpKfVBhFsPwh1# zO&S*4Qcs%u+w!xTonmws9@qPI0(?QT8|C|Gb)eciM%{H|6V#Uwr}siE3tauU|8BY&fa$=&DVGz#+e{` z?PN2^6SYzfX2a<%#o9yHKTq<=&{i*GtPlncu&wtMZ>HHz1kaE{E`FW{+G^U%B0mka z#58NNBwKs=KXsv=@1v}BaQn4zu6oh3gUWalIQBy(O~Z(Zv(QH?0OcAm)YxSv$#%nk%AD5Jq@U%y#9+2= zE;r?n3O|z{#BERO2oz?_iN(uRj8@>WT5!_gA3< z8TLjdDa6g$G)BmClsG^UV_Oo!A^8D8fld95yq3zb1++z(H4I@BT+b55= zjm-v6=hO!UfDcK7BIxjwI=`OUqH;ntoCQr!ninRuVeCD9g2AA|7MD{TFsdR;KrEru z(T%~eDpbKJ=HgU2L97)wF?V*b^JSAQPNdx^dexCnN3vz=&xldu5?&71()t2#+;{qT z88wLL0iY*C2u=2J18aZbq8Lam_RCw~Vr;4%2?R=_!roSNSdv5m^NrUO$+R)>MVmyF z44C8Vxpk|LAti8 zSbSs}vMDLF*fYgTqC4J>_?J3Sn*%3ThOg=KEzmGGzHo)QC#n>&7>V#PP4K%K=}l>( za0M_LXOAPxpPNJj5h0?MRL@6oWK&$iEvtJZk(CH<BRY zSk2|}2y*D5w0H$>(58((-UKC9xB_e(tf~GOttCRuCb6N)|&@o+!tzZ2#=(1?HE0az(_L4#5#ZZtcj^+yV^{<-i$#t zh@9#OCV>cfMy`1C@`~>Ic5FZ0&f)ws@=anys`vRK{D}Ov>mIO2b zOT~S2pf9e$Vgz%~U!|Ic4{gKZMbV{T_gZ5<3p8`%n9{?5Ta5~eF>&N!q{z_7tZ6JJ zB@u|?&V@3;q&Pth&03a2iPldw!*J@b~W^kfJY8!o8+>*X~=RO!}e zJQ-fi!p=9tmrfiOFGeMfr6H+TZLkC(Tqc6<1V{lJ#YV?sqQ;YoAL33}B?l798McW| zLd+J!CJuBC|WyQ0IN976*$+>)2!kN9N&u>MrM6?-u#lXirq* z#(_vnXgQ*@w8!82@X0YlzKWgN(`!t@L-NaQI=aLZeR2^`@dR%nbA@B z{r&v|XFl)|d^|invh)dXJ8=ap*8?v!SbPwQ{-wz=?3##xfFgTJ16p!S{g5UUh9Uvf zI-VxneVrg}T1AOWIe7edd`x0G^2q9_>kXYtA9AO=i$6Tp4Li)gq%SWf!5w`ZWGFb6 zHqtY4%pG6%ns&%J#3oN)-zMEjDk>jz^>V;oh-74r(qMtK88*kPkMdb z31N!KZX9O^BoB)y6J!??1;TvEr$lL!ZshWHnE*)Z9oZ+(U^q-3ggzYQnq=qo8_V4$i+@mDxVty*%znkTPvpnj#T6UzJ-df7 zZjn#k0RanPUw%(gUXB!Huwn*PG6F*CAe!JSsidxofhct{B-yWF^dIDg&(rDFo1V~Z z1&N|smFkx|heebyc_T(2UfuWC+&bQAY-JRrr`yxXB9AiD9xOYbvOR8oE%(es`RU3A z!#UsK>{!QQq-i0&UpjtdZe51C-Zzr7h6c%?A9&tJ*^fH22;C7tD3CUo^`q~~b&?Ju ziNE&lADmnW{;4Xna?}_1@W31f_UmD0lDZut;=@|IC1pc{hNF6d^EqcIPn;U^v@n1*eW~pasQn| zr}TXZctfZG_vf+}&#s+OJo85GY8>`!dR|$5JkBjPH*l1?U$O7+^TnBZg5XL#cgKiw zgmo-VZVgN@DO6>5IXGRv9TUIcuj%09uRr~NY@JhhWzn{7E3DYIZQEQ?#kOtRwr$%s zE4Ix_C8=P=yxI5r&cnXvzRbt@G{=}@^xpo~ul-*%^?Y7bPao(D+RL-wH$Gyne9~4W zhLaU~MjD;3=rmVnN#p$j>jmzaK8hk8UQjk`4KMNx+whjYzpi$V&zE!yYu(wPKDF6g z?yD1U*=Y&S-*4RHzaB^WFre99%_(fBlYCF}`FELu z?{j=Fo6?*1z1d->2{tN2_7+8a9#Qrm@q)*M{hvr1X&h?w+R_wI(8E9GlBM=2QO$0G zeh~Ze36h~c_dMSV$lD1{MO|PbU$AIQm^SzHJ#o$1bq@gN{_XpJKFzUs7+~Vo??qVI zjbh1&a!8^{{;`PzlayS4d&XYfUXQ*94QVuK<{ly%0pfws4a05723)^zL=OKL*kaPEb)AG6{*z1YOC`0Ox%eSRfh1FLg?rJ$b z-k&egX|+VyaKH|rD)0&8jr-gL6CSTO8sl*}1h8ALI_$WvCH^+SA|U7^tDP!D!Bi-h zJW55Tf;bt%CaH|b#+bJ-F0zQQ=X;_8dtv$JQh#P<+lpcy-D~(&9mvGD!yh_8x!p7Ne#rM|iJhz?JUZ0v( zZ*%xN&9tWLPYd+Ii7bXH~AV~k;DQ8BuTigjtFy!3fM4jV_8R0w6OfvhC+Q*^d?f{hu0u2!odnvFMLfj@2) ze*p=moKvD3tLc4En>;eU^uC42+B#iib%&PF=Gh{d&F3CkovC-?OX_(t7o5Dg4bI_R znv3Psi$^=UT=xuN4K|I_)0n6Ys5j75ml|U%`1#ZTJf=G0FnAfmy{#=1#@z+hd3X5D zc(wKWWyK9|O~mINF7>}l_dUhV#5}k(ns0GD=3JVr>{OCkE1o`AlICF3E+*JgAc2e? zu~=IL1Y_-lBCyE5S4)f6&9@f=*LrH#d*Kb@pvi3TT={6&^JJ*e%!ZerP5*cJ+b z@00a@LA8wd$Eo=B&QINY!Er{Djr(m+pLoCr>achl3LKu1g$3B@PWT$`sl>H>^OMEy z8*(UHWENt1IBz#Q^QTA3jHN9}Zh*%(P3`b!0^03rbNKH`G`EKpmbTp(E<>XyP;J$w zi-46wjLk18-sjTTh8xfE8`u2&5sWD>vIN-uZX`N7tVtsfU>PjA@?U!1BFA0rOzU2M zVu#e=b$0j^jCTdK|M!OTz5n>ZCFV7`OCGZOu}t@WEHkp~y8ZmRe>n5F5xq9hl|dcM z1VQL)$o%fZ-sLHL33}Adz~$}DHYOXx#g5VG_vEzU^}HS?v$fBhJmRi7-_7eu{4w(o z+K;6;o>Opm-WFJ`-4No&F}u#g)NtDYi#dkxf6xBP40GN)-Ut_%^1XfoZ{jxjI^h4N zF%T!(XAD2KwYlFm1>6Lbhb~xZG@27VOlB8bYdVko z`W{!zZ`5=o*Qw@lYB<==v@&?j5#F9IM<)Z=Y13JpKDo5@d8OK%1)Ok<)nVG>C~Do~mM)LGftAnX19g1Nc5Vv1rM*(YJRc0^Gmd~@jKg@u8Uk&)GtThMh7 zfkBP1-JXXl>YC4eduFP|2fwdR0@WnBs7%Y|Fgz5_uB*B>%vNR5a?e7ga_yLKCs0GYk|3R zomV`4Z_~~uK@76nfa--B>mH$1?HTtzpsvKd&!H@1AJlm+*X-gh+)E;=&0be%;Sje= z@!YBU$sTzH~!$n$pMlQOj- zojBBbe9(mfi!&A&Fv<1S7YMtqhu$1~fq0UG*)8}v{+BO9N}O{-7%uNiG;k7_uiKNi z+TQ!8L{3RFv(eG^3tlWt%?)3g-~FD$E3Y>+9MyU83B)%tPkWgQ^{XftV{3rVH>d2G zEI1TIZ__Qx`}#rxkRT$I)Zh(Nn|>ia)#D2PCn&5x&g=2oDQThciGb7XeIp`%9VLK( zkbmmo$>2T4JKuX9t@odK>3Iz$1_a9_UPRP16!8IJg144IZB=nR*8uQ<}F z$JJwvsh#f?L{*JFk#9DH+Vj3?h|@cYCt&eX8ow9v(3HdN8W30jz1kNn<&neBIa1W8 zT9ZkNMMzLEpHl2TjBhdmbrtSzfJ<*aOF)9_*8Db4Jc}6bxc5OwG(n@c+m%`@8n*S% zk5Nmz|2o@#+dO)J)Ys`I|LpcYXxdBnH6JgRlc|`bH+LZM+@X!`nLaG?tRqo zv1?%7EP@vgFPGapmz-{Hz{x~LiSw=Rea-Y;-_I;8{-eQXORe{a)G74_+c~=-!wKh@ z)f-UvT_@h!l=HnUg{d63qQKycNHCiYOKPo@#HKMWHuz5M|GMq`-^2cLfz`lq3ob4lCFzh4-AqPzxkHY zG75anbN)_;^M1@d5cIPk*Z;E5KdtcxE61_lZ~wY?Kc^L^<8z1ES(nQZs2go^IQ1Auv=qwRgw}LFve5!)9*=!8m+%SFLgZ>jo z_y$8MC@9t&48fSrgWXTD4{>_N1|3T1Ykk}28@}M`1>WEIFps6gVDmekgkPO@#8Ao4 z@|$s1qpU!cJV%Dlp5Fvt>E#izKaZXFN-n)e#6}`1B6EYy@NqY@PMwFxyfZl6xw*sE zKkhg!FZp1Y zuj1pTTDE6Ymp1o><^zH>MvbvhpCM~Xh6bPCK6)E%xm#U8pTA4}W0gvn`QERfn?b)= zT)~JfCQP6IfL$MHG{lw1>+FA`<7=EJwoI40|6`Uw6acurfC8GJ0}cv8gC>ckBV1xu z8K|-w60B{$fBP?$f zuZeR@PQ$J}4Ysh=Ufr3j?a^-gK6p#(z2N9=He>i$H*ys6ey@N;30Q|MDVEb%m9Dcqx z)~FmB&^TxB6~*0FW45yyY~S7Ss|*ju<1=(7Mjp=jN%tvXA(-#w8C9${+v6MEK>BfX z0(xaSRmm#K2b?+6e1&ySy3!OLp8)3Uy@8$1eTdgQOvEGHlzu%m(`6?gVeZ}v2QAoc z_f!qMu#)3&X}lDu1oq+&uNgstP=9JHleV>W{@;8SO;z z=G5cR&nFTudEyuUW_>@&|KJ`!g1E2gOWXyLuev`SY(+@sd&Jc2P2jid%&{s6Ouf4j z8s4m%6-@-RR<_PTK-VPq{J#~J<=fSuBWT~(H-+1A|A+eXof~AoGfw|#{;fDAKXQeR zbB5FH_8fPFx&v)Q|InE^J4y_PFGjy8eUG|zKdNJp@y&jtpu3OS z`6lDa=FhY0pILK!@8W3;w^WqOG^q)YFmX2CN&cVNU%$8P^2h5xyXTthw@@8?0uc9H z5zT?bpYWW;b3gyA4Ewwz+MV=&K9q0xKL6Z)oV|D%(M7zQGJKz)fE~FqiNh!h0O`pYL9{=Dw>jC7YTh8VNidq!ffoQG{j)<_)3$7vl4( zd;Srp-^bE>djAU{8#`N3GK!)jcns`hDPMfwE;rxTV)Q!K_)sYAZB}>3N!gQwjo8g4 zX*@~2_QZ_L>EiB!koz1S!ShXchJ|LF3KBUp>}uVSwf+}RHclFeD#jd#L(vzwwmX~S z`?2NwEJqI;ivb6+N2H;PQBIN+(^xs)s7Vi;h<1@l4nMD6SXw6=M;o)1FW@9Zoc<~W z*7#TjWDe2uFf-j!Fq_!l5KZCe{f@)T!eW-meu3q*UU#w)RWkBUJPwabgoE_YRo-w< zD6HmEcV4>Nzti(^i5reuk#%H*-aar??b&!*C-UF_FxwY4c*FDRD7w10G#ee*NxKP8 zI>+;kxVm#Jdaw%zwWNh*WXXt@f>Mbc8$-qSs)E1SjgZ73KbenXi8Hvuqe{aZ!ZS2hm8~X$K-$9%pGbA>%?$( zcq48-^$bkPe$&$AHlA*j*Bwt__bx8TWXPjRnL_LdGah&zC*uFQzHB2zpaG`$1JJXv zakm-1xxqgnJE;f#7KgtO{qsr zVM?)xo1lF3w)%pDd7l>6=O@KTxOrLR{qPzv3=6R^J#J6v;qoxn#gGatEG;Q%;*y$; zf564Z+-;{}9<G#x#xeREQK!}9~wVY7MWdgiop zBx59zha@jf*eo&{{pSot&*)y=X$#9gB*)0y?Q~{k7<(rchQm8g%CbF9PR=~7fVAs} zNH0fox6#g=i_HTs+G-!vx0kEs+`hYamEPAUH%IZ! zPq*cnOD%XCi_lPG6jAp?o~Bb~r)OQ*yv}hDQ(eE^=*bu?Tc{7c=Ly+_X@2xs_N{+7r(cRsKEmel?6 zUgOF6j7wctsJOFMg2=ppxqw_Owu#CR1@cv|OoR#T-q)w`<+f+YmVHmC{3G!Bsc&eq zwcTO(ZWMfVeq%-ESH$_1ofx<{i`>pL{8fu9gTxq9Ikl(^MNw;Otca%5%`=sZTCM5s zcBjkf<~c&PVkrvk_4)uSqrmtsnE82Te@ZHC+C0wCyZq=)r|zYoYu;*D0Jl|!X>}T_ z4PVa-Co8kk@%lkA-Hr#W3@+f(^BD*y55C=G3Gc1{LgTG~*ywbmOQ+wEfj`$jL@$#* zKgjL&Z&P^hPL*x?>Ns*{3k!#?+2jRAjsB8ER8|Lv(&92;jG?I(7Rit`@2s()9h;wd zvc3PAoWTFp_22tIxn`pm7%A8=-%Bu|!&~puVsimLvUmz048r-l!VNF2RHPDh{zp@c zA}r2ENI821$@%qI(d}wyJhr_rw(g&NOeA(c)6k%;c4xwz4j|pjxzN4KGrKO!iRE`l1A)ji?`R805xaA9(9PoMB9c1X$GBT>hE9-tdlJcxN{gM4nMT*V=m~dv*Lh7sZ0( zq0QsZliytVS#&PnP4pL}t#OSsm6h~Uk}92by2(`WpF42BEhi^u&V^1hQbQsr*-Yb~ z2!$m@mDTXY5K^L*IwWfxn7JN!JPrY(-cQUtN#^?_;O%>kf*vSyzzNJKlO|&4E+@W+ zd-qNzo#TJnoJ`CPK}$0`F7$a*e=?Kt_`PC+y%Ce3Mf^_G1qOHAZA?~%>`oc=!)ICo zn5BptobELK_uXmv_6Xr(TL2I#hAIxRYSR(CyDR=*d{(Qq25&|FX+3&zMVU)Y!q-`*1|s|;%!&B3B{)A>@iWpPMU z{=`52H`bjD)Xsoxdi( z*FzUf3Srj|)Ms|F31IYp(XR3SvTraM-ufM{TVOlrI^vfWEE&gIhm+wxtb`*L!v;x9 zzsMDb3M+x@gUE+Z>=KlKVhFwp^KC@;aoLzoyNmXLu;+j|}mi&(zbHk>s>M|GS#=^)b=x98i^*dd< zxw%<&zD(n#kjE!&ZB3hZ#_f(ILR2z>1wT}F+$sWKk}yt;NLj)7u~-IP@BEwSN2n8E zzeRS_BBqt$Ipc0#%eY4Pg&;4#P-B%!mnfTAYU40(Lu7Qv98^q*p|&16Km^$o^{@p5 zydXrVVB)xmT^AGFScsIUkwSq4&9l}*Re)m^iW?+vgoyg@4|3o61VgwKh{Qo|y#N$s zG!ziRhG;m**!==K-q|7 zLsEoaZLy)0=O)Mt3STWNYoKjqB~o{ky{wU6wurRq^+feDP}NYuKNM(^Nngh z1sZy(DFp;B#3_a==~+PQjap}%375@t-r!UqxS6v??cz*UTf6;Qn|v?^0F9Km_@C%QlkTMV6nsO zPYT!Zf8`_6FS4(eCuhqkI<2bj_gaOG6v72M4s~qe_|`QERHag~LF|x)+w5ax7R5KI z1IdR?_eso<)%03`k|YDRF*1)uXFDo7OKR{TC^igYvY_R1i@(~-8o^9Bl1tcp2mg{p z3_?jq5;1Dwhd`BxOQlp}gUD(X5(V->8<-(4pzO#NfrA<~v?;IO7~15wO{ZrE~p1Tlwr0}M5&x|*?cjW|qT3PvEw0wgg6pd@!^ zgecazpO~+v0qW>`&4Wh|Q0-y=Y>~JC2WNYAIGwj zmx*++;}5ZRUu&ly(kO9ax2_pY&{D4v2Fz6+GHaacX+(=t@1-_w*1G-;BdbLN4XJ2J zCjW(^-1x5`w_g)vk|xA~L8`?u?5AOFqo{T?^Xftupd;|dx9q%D943^)(LNzb!#FY- zoEkzY^t@UL+MEhWiVnj#^a8j^l#@b0%pXKlf;V2^>IE}kAiJ3kV#^NWZp)EgtU^=b zN`0+=x*lE*%P}r5nQm#~`KwchmjFaAsfQO71)V1`qo;z*3gDc@;Z%+3tP84{L4sE}1+tzdygjsf{< zPz>NCy7Fh_en9_(WomuNGAH#fJBODfQXc@ zQI{pHQhbzGRcg|q7?TqaL;y;0p>*D~X%mK=XA;w-k&9w~7%KY{C$q7h+v7xsTMIiz=HRO1C|3K`-HRFvWuz=u*?OoGq91lS1C_Q9!r`wv!l7J`mNEbQ@p+e5e6jAOBlhLI*H$hAkPLDTTzGfuFGL89yVXGc_sBo01UvdfM< z3lUMxJZ$nYnR3cTwu+q3QrcL0)1cP9t6bS`^~T$@bO^OSX|VsA3r^d>Aswl;=%q$P zCg8^&D%~7L`dGVC7hnEDZb1f}gcN<|B)gN@!Ju-^XGtg%3xe=1P4oLxZmJfyQfJvJ zEwvW3T5=r#L7UV18`XoRW2C(;R~8*!OflqIeOI!)mlLaI-T)m`nuI8h)Q9?p4>2@6 zUT%vBKW2$=6|1D`{*U#QuR9NLMmtue&Nu9Q>+juD&q`ivT(sl4$iYl+UmWL8`%8-I zINHKpdw;f$oE}4~k1w*eIfX;k6=r9h?a#6<#GEb~bRA20wegk0A(G`I`+>{1HQd_k zD^oWC)~oKN*jT~X>wyxae~XkD&3X^2;8B9`59YVIDN9(JTW5|6<02@qJaGw`Hm{$s zB>}s6trp;pHi?C(USUNfdUD*{)(KQf^-ystqc1-qVu=`1Z-Re>if4z!g;t+ybfm41 zkVOiEg8){#TF_N0)?pm!LZmv;f(J?FQvjaepmXk&`=*oDP%40X3Msfv9=nlSpY&CU z94v~vqcw(!O_eoR_18D2e)Z2fa?jdUHg@*!aOA|mfY3}Ts8|w;rDb@V-lRD8JOezv zaGB%^eyNHgnL3o1;>!IHS>UN&J$SI(b$^CXZG~oN#jjNAD|0?J=84}ecs*>4U=ro5 z3ZZ|Un8rwGt;CMH(yJsAS&2%N)xnx*B-$(tkya3Cc4(x5!0I{SS3302reI~eZ{d%z zbQSwyp04cgB?A+{-655Xwa|WXN<~}tDDqyArOM9v*CvG}d`Ht(3LsQV610v@tK-gV zLZ$QXgruQ0#7lC8pBa5d3YIE*-X@9E0FT)(sKl-KT1SxQ zZ&@=PG^DO7gF@M1DOk9&$y63`&ub2cEfCOI2x!o605O-44q`e}wdX26KWp`OTvF=`!qZ$2P3J#2J$SHxCt8uGT=ih?ty@v-rEdS?nGl6iSzs*_@(Ul`xcbfq&dp9 ziqw8ot1|`TZL=|Ii)lK}PIc`~PTqKv$-Pf;clma`4@D6uMSgs4R|U23VOg+1-sRca zq1s|)<_R+XwAp+Lw>J>a%A1NfO_78s0g5j-qs;R?wpv$JoyX*#oKMh&nP@#aK8vq+ zPgr$q>M+_HKSfL#Ey_Iw%cex3=6U2lD%H3WqR#^IO?2pfvDq=|)pkvGY3Ue#vGcP* z!-Te`>o=-_rwT&9mBaL#w18AE_5Lo=X>gJ&r3v|AOQ*E@&HO^qfU2l2aY(1u7f66x zF7{p*I2H+#_gdK{zg7K~t=^WIywW628bud_m$mf+t*JwHk(X{6N>?2_(6UEqJAKO<7g?5-PxVSZ zZ_diX1?SQSYsEL26e1GWDyvL!pDCWqx~{#*00`?a1lCm8o3~-t(z(&Fot?NKu3)Na zj35fr$d(=XWzITr@XDejC97PerqZsohWB=4?w}_R!-%NC+KiT3YFR@E{jjE&&QXc} zH?j4b&pMqUorh9-sp|MGEM8w`t!$(})p8R(bjXAx{Sg#)iEj#5Sd00AzSzhRmAzMr zBqustvzR4B-JtA+E)-qLG+`)#3_55aRj7pVk4tk?h2`4DfL zRfZ~e+DtxZVn&=O3UBul?!9;MW+c!(#E7Fma^$glcOBbWAMo~7_-FMl!9tZ-)$C<} z%Rb?~966v4eYjgKu)&Uoea-31J=45FselX$x43l_hLCwU1Il&T=ccLQak!{#JNYETTs_6ZUkA22+@)r4R{|W5~bi_>oL3Tuq?p&GR4uX zi~X$xvPFbLR{xjj6?yM6jOT*Y9l-R?CtBJlMzzDSFKxefNb7#wR?d^Xg4j8mSwW80sF$)yYW)=+|l0z!ld9(#8-D>a0oZDM53c>SQGPs72 zWRtUVo#JsPXd1gInEAbnOC%GZ_i(@&Ws`c)pAeJ)I5;?F9_`|i$dgq#$Zn&Iwc0L` zRVNdMuk-;fFyJR~?fQP3wJO%38Oq7;RStdWJH4szC2x8Gik5yHy=_jJ-;(!VsqncK zX*06yed}2>ZGc51C?t*|p=Njr@eb$UNO4BuGMs}$6$vg*8ljlK}X-j@7BKHv2(oN6on6~9v{ zvpnjD-?FJIRjLe@^>5Q=d!Jhc_OgHAjP0MjDsap@Rxr9G-0kTOvqlNnf1lHbZ*CyI z59j{Nqa9-vu9Xhl(#f@*UQKg)KCPK7K~t9hva|l(M?fMbC_=Z?4wcQ)n7f@Rpw!WU zXZr$@kETzQB91ad3Wa2*6sEdPwGNalmFXF-W|&J+>ytDalBE0gXM)+g$q2DLAW zF*|BPYagAXN0kZ8-`=nVJVH1wbV(9cl!8G_84JhC%iubS@Arc=X5M?9VcMfccKJpA z-U-3PIASoB)M(vUuf&g8=_K$<9&`&?R(5d6{UV{*So_<|C7!kbN}ZFgEIR5Q&YNeb z+zu*I;<;L)N^O4Sh;HmCd`u?A?76)wJx<@4>cR$j`3clN8`FOyk6QDMDZ-G-W+FYj zj?FT}nciIInc}l~=+`u1o(gB;GF?BhV=XgEZ1zw(uz4>Cxd1w%Ev)QJ3Hltsy12UC zCjJzh421>rSUgg~zn?A$RZfNCZ9c-C)iA1p+07g%0jy@8B@*EFgn8*tt!9fIIkOEGay;abRoVsThD;)drnpq^qlEJgK(DObuWM`^ z`oa>{e>!n>nQ|8j{3&-56?}U(yEoNK;+cOJL}BgvYG&gxm`!!EZ0&0v-u!Pdp^GYm z|D?{C^u1!^_+8ikp6V3i=&W+QD#s+rmC3dDCSLlql4~UhbvRHNjAb=M`p)Q2e@n!~ z1s)sY7j~B9DmAKZE5DYaqgN|emW*`xRu#AljjLcem}e{$!9b32us^c}&2FRY?g(FX zqmIaXsuVt~L#nwH!8y>?WD%3=QYaCRv90Em%+|Qe3OeQ#@qbQrE6v;4}?8n{lU_wg~P*j&6X#QG2RnNi8e5Da&~!# z2w9#=)6=^)8W+67jV%a9m#Stk3_B}KzTv`MU?$f83m%SMZmI&CXvRn22$Z<}o78zOHC|>XX!SWG zm&1$(wa#8=4UQC>4#702u%n?zJ1CM`|@M=go0h(;;~kS*)GVk6LGG)bfbC6yvaRx6q= zX>zv0tVK9kOhiaVTfh|svpOXps`8vRD_G+qxX?N+46yt#J@WZt4*~I`D8SPOl!5{@ zQW~K$d64BlCXR#PGMT^!P+=5kkd#0AXYQGm{(hoFKHoPq0p4_n`Sz8zZnCo_y--&* zKot^aI1E|}f6)c5#GzMvkPW^pTK-CVfeWM}Rh@-!Yi`?QP@eK;H&tr(anrorNs;HV zhk{kJY5^gJFT6$_$Jk3g#>tA9%5tJsNPxc`hAZVN)Sz2fP8fayrHC?ZiOX3i?3xDq z&Ogx{ep?n2%2t0ss7A|Zl}iM%BGq=qD}7(;*tCc&W7uw>E+BXztG=aSOE zo@5TOEIED{O}pJX)B-D(FP1|C2C>h`;(F2-WAUXgG5idXh(Z+5(5;rSWSeCiOENG+ zrja2UhOrD47bT%NlN7uY6lUFYh-}X?53KGwsC=1UmarPsibv2$>m1BU@OqC)4kza+ zEyTpnl#{*vM6gX{ghW?Ha=Ydp8UmJV)g zLSZ;2(INYhaR9xD9nbH^f=KRw3K3M3FRUs3=S_^9o&Lec6z5cF+D)%7i*H}wOb?Q2 z5rwKfF|L(p`WWgxz=xVd-bjm8U=j8=Cg58gz>qB(${jTdT|_p<%C$HR(*nUKZu}z; z8&rEB86-j`a*WoH!})YY;Hl>HMDy^FwTg9w&AGodEsQ8wL@8?WAAd{o^&%M>)pzX( z)eLHQzLtLU7)zge$!3m158bRIu93-`(PptAb}&8Zm!}xdpchE&2A;GHRT==w_y#PR z65?beX~qPq8Gb}jwvR7RjPwI8l35D>$QvbvB#K@bDQN~WWireNkGPc>&L?GstgGyE zu(rHpi~2q)qoy+e35RjwATxq?#m&_s*DzRP`{1ma_^mD#e+>(&WK?-}L5%{JNbHBy zT__2t9K)JOr3O)BSep(h#lo?SI9gbl4F)Or(F+UqT!OU)Nfg_tQ{+7CgsEtZT3r9a z$#3843J2pLSq>SBWUw3q1=VNpx?biZtuy8;URp-IIazw46IYg1_?if9;3qd&198Mqgs?bd$ahZgxNeRjAyU%olkE^-3JM<@%6%U(0%VyCHbC51p;mZ9xaGB-1e|lGvp#{j(7HRWhtmS4ML=O*ChBGSLZ97glYd zBy3OvAU*vrS<>>trntHnpH)vJQHwoh>sbxcQ8^Wf<%Gn#+9E3|@BG+rp|=@3gm#y# z&XB+f!&5e1<;)OH7>h_k!O_QF@TG6-{M<_X^k?oXZo7X~#bntdfV9kt&^&JP@F4a> zuB!=P6RV$Sogz6Jey@7wctTzaZr;cdG_OupHjwu*J`oB}_a|G1yUoHD$r4O`=S>S~ z^{@z*hw`PVDk?=;J*AmO*{c-RYGV4LJ?ke0drn8>gvL?M-)NOvEBHL85qu0BCv3j{ zftSx@6W(;>v{?tEH$#_;S~`vS!TAAc`8)x+i)}qGFH97|t+7Z)8P5fZ*C4ziz6iBE z+;|XO2IiE2y+v^u?)V1gHk6)utQuEbU2@aEC_3}=>j6lf^7oDNz8^Zk$w1M5b9U=#J043hC0uzZnONWDY=CuCk0E&O*iU1pe>No7Dh**%@xCD)Mk z2)M4QXws@(eNoC#WH_1hA*l0j7s|t!X&B>LUR_EPp1zFbfxl`yoA~`Q#on-(29QXA zQsI<=%)IJWh1%%j%8!pKXu3LnVGr9gQ%-9QV?~+~g)nFt^I!0=+)IKJH~W@a>2$@t z8|LpfX7UJ}%;qmMEu1HR1ifRx!sD6UuOPLuac(D38HC(<{H*EvTms#;i&%W4zVds4 z2nodlPV+A@aA@)Ysbd3F_#EchK49+L_0EfrpZxXi%g%;*{!#Pb z*q8wmM@Eh?if}=IlZXhmboTIN2!iT${J*?R-77$M5)#Ty@WQN1#otfWmHNK^U;Rnr zV-bTot6PPRd<*&G%9852^_#p>>Y%q8kaU|BSwmlm|gRIm@B(Mbhx1w0@I(iZx)r@@gSbuK|?t<#EM|d0mJ6@oG?{vJr06Bi(;IK(^Y} zlC9FEngxr);3@0c8{KAzAr*2I6iedT)fh@!Y~Wgh!;Ctv*zEjbhi?wjR<*H zZA)rwuvS*kUD#He?T`F;4#6t1`657vLDlgskN*xK#Y)I5lrs~cEu}MWsdFd!E9g^m zW_#kUXBIM9gkG&%U=rqk%gn~d|CJe%$f4?F<8N^+uGjrhXN|0CLQ$zgHiQw$RNHoe zb*sUV#!~iZ0~>acYT+y#v|m{^kUE~xKZX;bh4pJ}P7&RHe-x9z@S*!k zM@I$JGJ5>$&OEA$(s_lQQ%PHO>@<~D^DgE>JZoxT?(TpUFV z@S1pEC9GVUiWDqiqySf$pR8;zuO(BoI zz@8ZuMsWSNdBAhh*>=}+YyR{Pr~4JY20ySnl$Q8LzK&gHt58{ikn9(ZiS`#EUWt zsVwni*(iv0wuTxP#iB%oV$6QJ6h&qbTiV*5)+2sp$?@)NiG$@G zE#<$lG4_6vF!BLGhs)@XduZ*CW`5?IZ;d_BbzI^|=yZm}VC>Vth;STuuzx3Dj^gL& z&$@W+$n4ONCtFZC*+gid#sjGs=D!c5C8!x;!pg>|h@YPff2Uc}wr?S#Vv#!Z&?Hu; zHcx!LLHEaT{3%7#?#trx^4?6AiEoSghlqvA2tcDO+6zZy1E|EPrObIE#(R7e;N{*r zpWFoxF23%S(F}KYzlVV2KEx1kKo9N0{MedJkT&hHu&Gq+l7Uml{Hgb7&haFA_Jq(! z=sf*+kayHc;AA{`lr2mLcaSmOj)1v|a9L(`^NZ((#e}8n)qeLac zghqPew+l?qoJcm^R%rXBD4~8l5BlV6<*zVrrqeI%L@n z(=9Ph?Px?1YYH8T2#y&GHf~I_AYsc^$HB^f`8%mItN&j6e1mX zfJY=O|N7&OwFa?fM~U}v;kIOu0=%QOUSD1xgjd&*6VxXt*9ljRe-BVZEm!7$z}dvd z72QY)84i$UIXPUn%?J~fc0`N#f%6j;H88ggT6{?edA}$ZZ3s;$#LX6^&FQLo8?DVL zBE+b}Vl!D9@s|rKO?5Dn0;o?)^2Lh{F98qm^yG&JG!lbSH`FthI^%r5`u*iD(GgT>btpVQn9TwJQt$+xTz*1qPz zy}vX5Z`Oj(zv%oz11avOy$5Z$>ezVylReMJ&NGjTnO508JEDUm&^*6qbGjY$lk3mh zW}B_9D9V4Ih_ktF80NYU+yq{a=z0F1o)WRzgSPE^lTESJ8jk~_+mA1EOaAXKoPcU1Y*QRU4&s&UNh$y&uCnuQsW((-N+-E_}9oL9Y$qgbtx7GUPq>20P zf^VceXeagpzOdd;{X-v!g5oa;8{5z4yq-r7t$d&3l$m^QK6V{1PB5^&FB8dzp2A=i zq_$?BPJQa@Ra0*Si1BgagP_Gnk%thWDk$U-w-qB++yVI2MFk~<#5>?cpyQCrEdiPb zJL`TCCR+3R8;&v^|HA@|z2pn_V7i1T=#gSM-?~I#{&0M(*B`EH&oi8j)erwAm}Mj0 zp8&4&`8*A#a{oPmy%RI}HydfFG*XcH^FQs<`$XY{aUbFpX*&qCl8@U(_lg?9n-pA+ z?$bqnwZEb7Gb>)Axgo-cCj{8^RPmN3)Ei0hOT~f@-`LrW!2K(!U6L`@8{g=;qC7ILBju==5_uHX zY_aM7@^GC-vI+6q<7d6i`m}(Iks*`KyUUpWJ8+s2(?;i&g0##x>7shA`Ql1)qwYeQ zi#{Y)qVIF%+xQ#ptd-Z}Mb$ztC3I&&rM)*mX5)ySd&xsprs#*3b9GeOwW* zDygEoy5HzdE#o`~JYI(Tm)7{ZC>V@Uzu`g~wGb6IS_19;56sdy?~e{@IZBHSI*1i@ zR}}#hkmpgCPU?Z?T`Ql?F2KuPAN#qH-0I5;36_(UEAe+K4%?moEFH7$mPRryoQ-a` z@2M|d=*5z7r;+&|gsW@WIyaT|EY1dkvXpYh-n7ujMdBG`0ky6x@5An26aP~6jdv#X zncWv^bsTSvJ{#VVo_f799A)~(_V$z%7MiX_hnb>&$_{@l_A`AqT&JsEI)7u_=Jf5s zn&$DFFTW3G1VWyP@%nsmZzsn7*Oh*fpE+|QmNFG}KIy*py)*xEFV;8R5;mL}4elcp zOO_Bs0;gRs<)bs$K`S97IJ)%AzFd4y^-oi%b%Qp6f>Rxi!&HsD^P4}&l)60`(_}gi%>zp6X`~1*H9X3~`jquA zxCl-P)LoB980u0@)OY?50E|F$zX87c>)-RsUoYd}-XvpbCt@WCyZyW<41(pH9#}qF z-KUfSN1t*L(!kUmY<=koRoGWY6h89r<{GM#4KbC2=b>dg$f?2DW?Omg3kzd>7g6t`V-F;74EQ+75hZoc6L zmOb@Hp52|{nyWs}#@~JeWBlb@{F$#Yrm%o?-FlvU=subvqnI#$4#i=EO;5hUhQ=0Z z7A_{V=^5U5^)(7-UB}=F;~CX2MB|zl8CqFLx?edD{NkHD{boI%`_5k(H@FCGH=~h0 z|9yv&_SFU@DHHBGU#?4pFW7YG& zXZ^av%$_@s^2$QCZheh?bs5H;`x(w1q?064Z4c=jY24>}V?B z%Fq6oLFpB&e*VAMx^x8_SFfc1#3fXemth!QbXK7J2T|EV%WNO-iwq6kJD~RxkDLy5 zfUmRrV=)vp*s^6c%|@Kz!^d#=wHJx12>q%{XxX=tAN}%fMo*Z-Ws5KO<)}L8GW|^I~=)ijZ z^!!uQjy#*v>T05Hf`arxB5D+d3GL~6ES18H#yEWNT^cGTbLIcOm7&jnj`XTu;TnT5 zWILwmWZTr}{6t9qFT(q4lBc$IvA1-G_qzLiyw~@1&+!{~K@%9=BuH=BzBEkQ+FN+x zr59*TXON+C{_cza%E5Q{@aUS|R7@Pj{?+68$uF*B>3x4oaclxwW}K1d@QHuAl&hx-GMRQVmcqc#GbwW8 zO8|z*S!+C`hm?Kx*fc>w~#8U31j19WZK$ze&c@bUEjNU{c;(lVNKg#MKid3*g13KIu7 zx1t5TbvcbYHnMI1VG<4R(6nz0%}pUzyq+Q=M=@w1G&XKx_r6AUG^eR+YbLXM9fvo( z$Wu?>Pvwkh>|C{t!W}mDo?Yxam>^nH%f~)`8Ko7aI5ysLVLGQQ=uVd8kJsLezY`tj zr%N5dGTzp<6dO0!Q&lsV>Y6zHD~pNBMs~b;KZjra9nqGznLcA0v9Q6e11%&wDD5dqsi& z1T&1_%)6ppQV`i*7| zb(px0&Bh)3$rzI1BTISa)jc%n$;>+UJYHJ0pF{3s29G|AN8fmp*5PvrjXR&>Aww86 zq93CMMyLuYq_BAY&32mfY{pNS#Qr_|*tT{*TVGvDVf$`$-40$jU{f^nLP|wD+jnlK zMHtj?U4!%PI<~#_48>JN)Q+sAw#-GSjQ654kn@2M38a2sd)4vu#GXG&b^Z&QupxPF z)jGD8jA78!864R7DjTC!gvZv<_V(Spv*}4*Ui~Hqiih#+mU2XV27_WPq*~VT>dGf5 z9XXr<#y<3_U*R_G=0HO|!=}vSnP)ao7VS^%fD)SayvVzopC)P;Xs3-Qo_&z|;u`+E z^bn0Z3mDMy3eLW#DXSes#_r(NwJUgf`$q0x-%P?7!|q33ri-3a|MPWiH72&54JFW>bk5U{X(IQMfs>zvJ$bNg@-}^MJ zBNxD>y^Y9)K{Dm==B5r-y<5t&`)XOg%Vq!ScUZr)mC-}yGHl2&x~4NanNTI)Aa%Tw zrn{)q6Qux;xsGPY$XQ%<#mA|Q*(fJTvaXT#)%$5Ue30FT4$!{;5H~KE&H2L$=-BfH z!z#l}nJ|_Y?|F!Ug=JiP{pTsK8cs3+GtRo0YcHBlZ7fMkeFJG_Q$7GHOCwmWX8y%D zG54(TL^6qNMz8MLyIWq$liN1$7X^I*@HbqPL?dAmhntu>po;6x9!5;10EfD@f2GRW z!HfzhAE5Z!KYx={?L2frHQ)Ny*Li1G0|To^(p=X}aV1oimrxLoW0sBNo(Eo_{!lx$ z{c1V5@m22m{ulVeA0Hx>wEPqV>Ik0a2axtvcY+fe=chuB2q`_e4QS;e!-YhvCQvnD z6!nhFinTUUYF>ZwSG@h!3Us7|@X)dJA6w1PArVHGTD)-o&-luh|BGc?nt1m0Mr^l| zJ#W9r?rpELYxi!<(W7W;Ns(Cg4qKkxN~sfKL{W+;o>RjM{1Vym-Cck0>G`|Eo`Kve zE-q%@zJ1(#@4Xm?K|w)*mjX`aq{k^AKlu^7UB7qt$v=P6p5c^yJk|=Wuxv?7CXC}K zOheI;O0ucJp={VxLayfWOD|*LMHlhrs#kdC=||bSXD7{d``G!;+tekKESx`=3+J9g zQK*D4X<`wFlu<}L7Ut78T+3H(|0IVJ8D87lfNe&+&Ck#%M=)vZL~gtJCelPHs2t9h zzw(c?Ct7&r)fZW|>_wcmX7=ygLW`rh<8N-~GuJF;_1pV-eC29N2S{dJbu+6DSX9&u zWz6`gWKs??9Kr}eDCBMG1_r&!Xq3;NV_kQ&2`>NqiG1GgI(kDIh!hr4W?B@v(4_j| z#46F+&%NjikB{iYDI3`@G}?6tN5W801PYY0DK*nX1?WDh{3oj?^qsu;VMVN@KhrP1 zff55+x4cfskhI#0++kqZSf)s!jk{|{=f6kLl2<=tD_FmEfmCJpj!z6+NdO6 zSjAggR}V^cEt%mbKl%YrKJ^^Uhb^wS>?ZE~)J?>T z3?z!8qJEV3>rY5PX~bc{{Ij|C%IoM~Igq-Y2l&O0e$10kK25U&?RFckoAD;dye^)D z^+!@)$KS~q=chv*AW+Jox@r*T%~=d?m~@BB-ggs(a8YgVP`7;r*IqJ@#UHzpe&zir zA25&`ue+9E)nh10nv|I36o-nq?7|EA*vHPtif9^yiITplMYt$GOkL-y?ets<^7kM- zJt;4OR|A%9Gi2Ck=FGX2M2p3s>i%3f|7;4z4Cn2&OHob-j*?hzDKn;C$d_-pkkO-S zX-(8qJ!}+9Zn=YrL#JcyX<@_K-DGTqt6Jz6b6K$Hd@j4?Rt|PV891(n2{X>4vALDK z2imy!##{N+O`k$J4Jg&Z^mz+eeAA5-ScSwbkf!0S1`z6~R>r;>AOoYN+lNnIf#RvE zqgQ3CAkUv5(cZ+9f4-lef9u;+n2KGS4+7AU)|jpZ0<`q<(}F%N6-+Hb2?@$#cug^v zEIyyZnO5dpeLbJK+suO;jQr6wQCoTKl(U{`bOSa_ZIsP9Aw$jm$4kU{hCEo84j;J z{s2aC4RfxzfedjjIQKlh^x4mnNJ}gY=wQuaKIu;1-uL%DP3y=HXb?i+I1Z!7O(AAG zY+SaQ>gh8mtQd~jp|KAuZus;q%szJx((v+Cduh*d>kf~GC;Li*zRi94pT25yz2zr> z)|#XnC6l%>lC2b!mT}>EvsnGu13dTIo2=T`!c$K_kIbZa>(%8PZm46zlyTg0Ez&d>kt zi?lVR*t@5hkP-HtM9CiUV!nf+cjNEG$N76LM}$%s5)3K9b%@5x7(8Y+3qN)#G8ARw z_GW}oNE(R?S=zUMK`4(LlA*Eww8=`Azq6Ok`xB)1wGpdZN87&5*yS@wMg(hj?xcTv z3vr!h+vXQ>4Z$9>ns5Hqwu;9FDG`mSm`M%KmS;tYy zXOLxClvh;~Uhy3FerE-hC8b<;L=Tk(nfgs)LMnY0o4nHlj>&_ptn5{uxmmW$q>o9fPKg;m)DwQdCmT8+ZMdKmPYq?1~lATrz}V<&`|~%e#3iRmVkRi#T31v_rY6*l1@+D~$u>FB=U!n>_*Hu3SErhxl06^oHK= zI>0kvwY9affA>z_u8&ewF_m>In~3RBiexjPnnC>J_kW;j{6VJ9E+-szDF9@m5j#{v z+RC8QCW>k@X&ZDHg+^)@Lxa+O9&B-H0n-y5Pr3y~F6!dFtfMc>@Q$Yrp=^+v`=5S_eFr)?cUlwxN7>kx zf#7dW7S%sAM7wo&|asGYN zR%$0sW!I+t{P(MGG9=T;rSGjgDpq9N#Iy}^?wk*Va|65aR0 zJiicHdmER^Xqb^zl|1szAF^@AY}zA3S^Bfzu-;5DZtN5)`_-$p;D39;)j?)lyW zY;HMBeClXi*WlYf_#Ty%vSjKd$hNz9<)H^T^wMGCqv!C_fdpGOXv#$m8OOmD3d?oT zj*Zb?k0e8*0}T$;zq1uvH9^uKRaihMog^Ar()a$VxbPU zBs58?aM6C0QSjbJcNu+9=FH=%BhXuf_5yXumt&DhdiqSycQEZ@l~qCR}?rdz=HH(-d0oaQD9~W5D_6aqc;DF{B`W+xlQ` z%SLK*zOr6p%O)Ge|G|l zYR_ix-mTQNw({7Me<9Ppi~ZYS^|A)`?CfCr*hxe!!Sg@*AyFA(?yOshZu~26zW5w_ zLj$O-7|$E4wom}!do39jsj?cQ+?4`PsTWZFXTwRok@~Q1M9jju4D7k zU!P;jEbGk;pZ}9LKVY-nd74D+uKP0 z+DY`UP4VLL=Xvw{|3sZJmhn?B$KG#|v7kMjA>`WGzy+x_N{CL9J%8Wzp5CvkolGf3 zC=_DLmM#3}fBq+Lz4aFHc$~ZMzMH0|Ccf~6FJKr3+Erlm-b!)OC@{XZwzjf-`EusX zn@3?`AuCp_ARG=eZQ3*%8yi`(W)0J)Pp4nMex365_J+F4>nlWVd4N71t>`#O02#-n zUr8a?oE4#5)!`P#xqMP3gWPtM7e+0@Lusna-X!XOq+xgE<&u5)-!6%So&3z zV`ozSv~lM8HBU$K_HOZZ#wHwN(B#V)*tUjvNe$D7RuRs00D)EgbmV=$`$VHeh?!#t zF*=rJ$bgZwoqHXrZFQ6wg4t82kl3-CcB_?g-A+YG37HN^Co@DN!?3A zJKccxX+v7$XcsG9z}d5gGh<361(_M_-yLK0s2V1mJ)Lc>&(PEX#j%lGebL{5o96ua zV=q1EO3<~4iFtdgdtC4U~l#Az#V)(dPUQYC?q+%Fn*Ob%NaFFKKR?eQY zkVLYMqT-=Um@eMW+?k{>V-t=^&RIAQWe99L#l*8lQW7e~Oie(Q z#Ti{)&4M|T*}U^0BPs>evxc*2WgXKdF2Jp=X3*>?rGv+E-mEC5Q%8t$Y=49bjx_jp zTIPy4$x45I_`xx*l;FDF^-5_jS~Q=I)+dO%&0H{N4r|>yI$9fPXg&hzX!@z<< z&cAp8>({Je;*cRsoqIMS8spi=pQfs;hH=xTlBl?egOLqnIwIV>Yp6)`W~o7R@j7!WfkyLcM4 z(r$zf001BWNkl5-klR8unvFCDFq5G#^f5r0b|2HjwicpHE}QVbYevm~$_p$u8qyLp$>)52M(2 zG5X-v3Y=_^i%CC|4PPfp!9I(gSkx%Mu^k3YpGT;omZA`ZCJm*0+%N`5CbH$p$B zj~!1%c^S$|GGyLW$f{aw+sVFwQp(@cp=TcCe?JrZdgo`B*@H^C)YJ~-Z@=^J85_al#a4jvbDbaj2M-?P!3Q7Yrkie}qN0L_AAXoGPIX-Fa5SEgN6j-i834tj92ZUh~j>bUSWHM=N zt$+|AGlWnsLb-&ZQ9zSPrBFtQa3qFyZ5-Ps6bfOO29A@)&R8e}rqpQd0tS&tA;OT@ zmW5$Tq!~tKGGuHEr3HpG5QYKTB@zyUkYv&w2q6eXVhF9UZ3ll7PD}wtI0Uv$I+?^a zBuavz4TJ&*O;J$+jsJN)m7l$d%vql$=}5xL+qJZ_XC=3c6R&tqyoY^INTHNON*943 z5{_a-LfFbColGILL@0rh-VU9XWl>gM$#3rZHI$uoTpj{WGEW%PFOo17S;5rtzWfP6Z z0TU~oMk{YqMCDjWQ)9a>+9pUOhHxF6vi`(ZK1=A{FT?-3pR_p|TiafAKp*=2iL^{_ zU0SDA9SVY7bhVU3BN5DS7^N~~QfZWOFijIJ1diqS&T0wL+vzwGiO|;8#vlIh2R`wM zPf$@&K`NDcALLAc2>`9p!bFBbXeWgsHPL7sq=}VDgD?q)BDk)DZP|pw2GTHa?2OmH zr9mVbMHm7nokXDtM+*RrZCQlEF+k#^)419u90_AP3PVT?37K>n$JLmj2$7hFPaV4h z$924A&$htO5Q~MtFi^IIFeO4n(8?ucd9f`*E21G2$IW189DnaX?HS~y#4IX-?_5W0 zL?P)Lenz^@MxroeUk$v+y875_%ktARxV{1+U-#{vSW4t!xg$>hN?WDL>McI+2gnRggN$@N$ZpVN4^0b}|S9 zlyS=oa2+RSc=2r#u~>}neeZjG_Rh~zTw07}Sx4aa-k_V4(I!VLqc5vq zJKyACXS7C`VT6<@*C7%zFhe1fog$sF(3qHJ7!WAOCKioAC`2Zcz_uJr0cOawK5pt5K=_YPKI~ib(#5ipSm{I_nm4>54IZdGVx6#mT~8U(PXTpyaw%LlfEo_A)9M=syj)C~ zw2hR$9m(*PyCF16S!5g=1h}q^l}du}pq8C+y~sjgfbf#{BV435kkUX1g+}9~)3ha% zUNnOAje}OgTUKmJiYI=Ym}`@0Yei{=mL^hxmY%$JT?gBdNGZ@dFZwcQppPg^0T6I3 zi$o^lyXiGLZQ}}s)Lz?J8Ha34f%Z4%@~r0sZEtFD5h$de16KNxMn|InXq*`^JXjr} z{rSzxWJo2GIRi$a;VXwqg49S{+)M`0C|6)vo)OLi{epBd z;o$|qwms!W3*kA+90xm*@L;FE8YPoz&dzh0q*tyM5-l_-+w$g!>^@v3NP%UgvC>KZ zzFZbRiGhQrJz@KKB|RsS)TvGztN^rUyd0X@m3J~&(wawkoAY>Wqx&dkj%AvWUH*1Q zc}uzj*`PI!jBnmwjc7Fl0c9@&l!s>3r&Nr4vNrjy_r7*ZohVRr}=hJUX_rQ4CX zzC0IbTr!sBal*y>OA>H#wI+$LSLDZfI-ZP5BrP;5FQQ0bVxXn)8rBuZTsHmTrogzX zI^|MyJRX!jjgMa+C5K^G*47TlW z*ysn1x`3^c{te#v(!JQ%r@OW}F*i}~=xEZaHttUc{G_psZB3o{VE-Y2QA80XX1#jS$H9z;@8%i+w<-Yda~G zo4>W+LzHddSPmc%hKW!f&ozpUWC!Su+`N|rgy*btT!pX_d9`(r?nt16JYE?atvfI< z2%Od4CgRfH?(4%r`TKYQS!1bXS^gESAJuFCu8%9c7PG8mZcl&!7iBEcX$x0qOf(kl zC_ngBVS?>pM~4&=;TeCk16iV#%I=-imwm+o!*1|5olcWZr?dJ?a7}=t0%P&}l7m;} zdCUTeL?WFYG&pyxe7ZfAAC%*l=zaHjp-!nF@kF%{64@m@Ui*O9@QokZJ4nz20&q@V z=ghy`BLf|*-3jt&`O3X#Bs6>g?_c1%!nE(rQ_73V$Wi{ZqJ#hQWUuzXci<7pUDnOA z5^>DDeffEWK0IRQ*p#&=`i2$>2R3W0$N}gK?%Z>JyJs8WbzFLGGL;VoxSf$ zBTYX7Iua+02ehU3nd-Tt5GZd02jLlaHJ%U5o4Za1g#KVrwwoD5M4q~Xm;zBOHlu2MbdHOE>&c=}S;^i4U8U|^a-OcZ#7z%vR8ZDJ@* z)HI2Q!F4muTtE|(Y`>H${3o_d-61-(`p2YT9A$&ssIsRzx zn8!IIsL=OYNTe2MPmS;bSo~BAUK!eVSm&$u{v4{jIl#*YsC=i@N7OxejT*GQheZMd z0{AW%q`y2<`A!JmjpRj68U_N1_MJzaj9&SqrJqZ%r#^dTI8sjy`9=*Nlgc9opwBDW z`FAdW_v8urQ!xh~Us*D*FG8o4R!U$9!&`}~0-3K+(#Sr8tCVN@^<|4^EcQ^kA%dsP zO&&r8c9sB-`npF?@?F7+C3ZRkD33+CAVYx=e$>D6F3RTYl^)*=t+F`X)&9y!DYJfS z@QnA`)Scpuq`=29zGKRtn?6iFyqv7=suKtDGiM|8CeN-V1lns5^aWZ;e+4TDWq|2f zx(t6l^+3IMZ-A$aU}yktU{RAM$1}|B1{uOry$0X~&ImjeXZRBi#l zKB6sx-!gZtRK7RE+h00+zh7o=RQ`NOBD?$!%J>Mj1eD`YUI0>-1S0E|JvzfqNoW7S zl0P+e@_gp!ggVn$+ugWR+32?1QEx2ffUr^?U{L<%GC@hwSMIa-bkirZQ4M*KFH&lh zW8*oD@}?mlumpydfU=y=39pYRkGi6y7p&B_ys2`lYY<^n_vZoS&Tx=*TR8vp!itPDJPY zres^~m`1NNzdcSBb&`Eb=42IQ$BORc#K?jB(}i+I3GK#!boKjgu^mLT550Zl|jmtt|foe5XYvjXtp~IGEX@5c;APW0t_#+?9m9{ zNa39eR+1uv5SKu%`z2(9E%Ny+QBr61)~-FdTh4z7n!;0+WR{nSay;{exK{EAbyR0lQ{`Ic3{zR(BkC#xdXfjZl4@FY-lSkci(_0+rMfi!V5lEka968y&yeDAW z{Z*adu5YC6DxYNDv0Q+t%qvG`J(S(ulRXw(F9SfCE7M=Fl6xM%yueJ+Ie_$|#3Vh@ z5pvT*BPe`8sDijJ58^2)^NhQ}%_?x;A(iZs21cT!Kz1=QoVvO6+3Sz&YN8Zmn1KhPTshI+`LWK-`N3WfONA}_ADv7n?*mI)Dx!Y={oRMRcSv0 z$@L>+Wnlb0%}_|E_mdt1BVW<;ugb@T0p*N%_zu1786a;6`;~Z`eamb?GEaJVkzRP? zBd-jQSp&J3YC(ZAyp%fm>%h9q5uIkOqu`m&u^FenZF*iA)BQC)l#&-T;UDr}IuD_H zhl6i2*XB9$)yGclto+{+FOta3Yj?vxmnZY2%ITeWqUY!vznos<;Z)PH=Kf@L zO#jNPFz&+NtlSFvUp{^U&v=!S`}y-|eo)vE4|rT}bf!>FvS(j4>rKrb0K7a0 zrt)~-X?@H|u*-DkR1VC_@IFNFz8|EO&i3gefzo?Td7v;~X6Bvt`bRfnuiHq59GDLJ zRgbSDJN@Z&;OO_}>d19YZ;72$!dHk4Um?mxi5(e5th>~oyFhmN+g+Qnq8USIx>r{jzOoE=YhJ=>Dm0WO;QOzziQS)metL*1lPb*>{JdDc)4-A z#3^@TqD1BwPkWILu=I*d>{`aHz+apstxOXcL^#|{wYuQ{2=rG#Ku$aIHR)C z-4o*qgttM4tNkUEc^{>Nx%Gq|?E^Aj^n!b z{Z@6)Y+j^A28cieA~ApjNr0r7C5n_NQIWEOWyw~wBCoG?ZC}4@Tek0el4VP>B4s6u zA{9wdq{Iw@2_ygl1V9o*CURVa1+ZzRtKJ_~)m_~)v%3HR5F37<53n;m-90^3r_MQb zqB_0J98z#ic9SO(Xf%-qp}pT?A6v)eR&=ZrWP|S&S&3B6jpihwz{TbIPo=EREp>K- zUOMXTb5|EQDPJPo?aX^yXF}K?qXlsd)BXe1MWRFn=RU$qGE#AQIDbw{;zNb+M#}J- z0Ui@0%U`mbz#pn)oho!fYt3ih>~~N zv69Y9t{IBH|NHO$=|pin@zAdHovw7@Ne)c!a|Yf4oGv&@^wkla zel0;0SRYf62+;u?Cv^FRZ=0#0LE14R_@$Ln^-K%gK;nGNp>BGwIdn|bid#Wp4 zWg?ZN4YE*}evUeh)ae=_-d#=*NhEz5XKG0U)3M&5QjUMQf%8-rWt~bAw050IZN@-< zrS}Y-A_S&kkD)LZy=$PI?J$NhpmcG*{SM_4v0XDZX-b&%jNW@QPVstD5DBxEV|yHc zEwf?lHNvC;3EQm6K9kAzvrI{2>gc7?fcptt%3PW1Iq`deO-O(Z2T z6-xv~B0DC2BQ-(!#$AbV$}<6IZFCux7y<&obq|M-I4Qk9(6*&#t&82CTXLyqnh`n< z)i#~IO`|+lZVe<-8-WnnZDF7V#aPFe#$enZ#=I+3;4e|;v_!jc>nSp2zFfvY%HwKy ziXyauy&~}?NeM!DdZvk+*Q9+?J}(F~{b!z3QaZ4d$N0ut*FaUI&WrmY*>kkO%2xBQ zPwiRD(yk*GjnT_XO^s%J0xK~TT1Zo>OBTD7x7j$|WZ#rD3r7T&KLJ`3_pa5+by{TB zOAI%!XDZy-y8T&ZY`dggceZh^rPp<4&!n6gAUhuB0!`6KVD0q8WNxGoaloFibI@=1 zPsf8;E+6-0#z8G(;hV-u9{WCL;4N2!S4W_;wF3umT!%snB!7Omss7 z`I>t2xTul=&T+zUvLWWo$wwMZp|b!$n9q@`sRE$Bv4KFI$pY|9MFn0VN>6bekUMMZ zP`Nyaq14njAVSeYYCjnYWF_8qCMu_z((Tak)2Jee0FXhRd`+FXyVCuu=5*HCo&;d^ z%(a{yt8TB+#GTgjRZvrlppN=n7p;lOIx4E|kI>zep*9AfQ$&WG}PT^B4pWza&WT!N{m zS2oq&=?cys9iyl8+Z_Rw-X}s=yt}4{TwZ%G?D)3lb5L7LPUZ<4a<+qYKhq`4Z3d4Q z@hx0&4l+88nWci=H-$CT&h|)ML;dSU?`I@D!%Q6cX8)FVO`E1uDcvN+wgpO=vQ!!_-!B3G5`n>1f` zD2_u}tRypg0_~ryQ3XTus;UaA>JZ^z8tOwN)d)BCT|QcGQ1-j{0TmwKacE7HsPw7Z zq@}4A<=wW<#GAc7vUalSx;kXu*bC|!2ayYM-TamTq;S0CrxZxT+fA^B&eY#vAcFU} zaWa&neu|_aTXIYw(AM?oRBhv329%8~Q3@hY;qMK6xHrIG{IQNMkb#ZX5CWYJ%NxLP zqX&cLA~B?Mbl9j11VYHuT$i*rO|l?ub;$bpdkB^6mVE9m`clM2cPMA)5EWkc6y058 zjYa}jn!$_QjYBa8aN;O4fp%P_eI2L4XHTzmn*mgYPqWTiBa%aIlWBrm^cf;sMdf8K zGp|qh8BYcCIG$U0yj>e>wZQWCey`7$@idotC$i_pFDvXI9ba)Yt~J6B=NjNCW#RVZ zUG2+7x#(vZf0%l!O$r!gJ=K=!_j;SxX+ovQNdJvr!&fVv)*<`NSRTg@eJy%k_OgWN zar=TmV4~U`{rCDgIPo7K{JoNZgxi$fv)s3&WI)-+5J@RhWO<%5fQl%Ux7{Jht}eOY z6a;4U4+C$PI9-vYujPBP6%ie>$0X_RNnfXM2HWg;UBIO(O8N!-Mcsn4>iERnX9FbD^wW)XwE5rwK>1^F5R)~K2Pr+nSazJ001BWNklO636Il_D`Ab|>(IdMmQDnI6T;quSLvN|?N#@~a~M z3LPJ(SMZtF17*fIB|kI#McV8+NUa}#gD*1CEvK8$_>W)ln-yrAaaHd39Pjr@%qFH> zQ5}B-{o6+wPUGz9uBNx3`^TwdSV>*eD`~zwKk(&XmV=Z})kuz)Gq!s!AUpliGXf`c z91x|RsJp#=pO>v&!0~5o$2=)?k*tGqWk^y+02^haw6u+>?dS_lTtzURgN$NdVeI?w zjHHp4w}$qU(t*DIWz7Hzh?FUB zASuuR;+xS>ZAHtWE=cmFBxXR#|H=z1po5?VV{Vtw&`H?S`gfldj5YQpfpQlqx%C zmC)x5ypA|knT=?vf!Ei3Pl4CM8dS3?ZER>go*Tu_>Lyobj`{DiMjLaEG47g%Ey?zg z)cM-mHkO0VJioL@mGXbjfCuQLB5SjE=u0wh0H*-S7CgNPbjO^+g?FCMOLS&U`(6h3 z4Oz})-f62Wrj3iP+FAKK%GCXmXLJdP+TOKXZ}FH$Wo~gpDELGm7d@Dauga7 zD=6hyN;$cb{k%USp;Tp?nVC-KMz(4xkxE%-s2O)R-agf*>0hSi(wE}?U#UQ8U3ND5 zH+*j6#@d53eA*hWZhnMY1=3qFv3_>u(07mQw9;yuS^ZL$`iYO{zFypNOvwqSPMSdJ zGIGM1Qgn7>dm_KD7b7i=GtL5tk7wt|m!qlrA>N&{<0>+r`ZMU4pe}!h0hC8V^RD=u zPH8#@{tPCrIA;j3x{+442WA-w9^1Lj9Nu%LBFobfZshi{@raYmo6~J|EGZoNZuDa5 zNa+fixbk?lb7*8alBP8L!tpR;@^JSM@qOIBp!`n4MezFe7~kX3SENIk9h1~3C1ZQG zQYsZmt`w5+Jr(=Ni`gpERd4Qa*EQ}bN(g%pW2 z_B@^{j-hZf;!ql&E|Br6b6e4@%eB9ad(Yv!hCs&gD;BX%W~tvcE>cPm5XA=IWZvL7 z+Jl{PR5H+(Lja3FbiaCinjh}yR07VLtz;6p<%x$fo;T+-vQ!(N<8yRCiuf~?qSA8P z(DJ{puOiKr6Dg=HeQ7UtPI~_*#$De`=p3!>+4pIEwZQ#w$Bu_rL| z=lHVIixMc5^{@zKjaE{c#zqnX1vY#)-lO&03NQeNa%{?It{i;vd=SErFHVOMhGgUjU;x4_?;-SS|Fq)rOaqN zemH>H{U$cGgvUF%9C+W89L*NUOyspc`>tG)MYe!%xkMfR6d?v8c*{X6-Gg$NuPt;s zi!`%KCPcsqMDU&%5~eHhf1j2hRHB+F>4-K$OBoZMOmQadoi~8f4w?CzJwAW|Sh(~Z z87o#1N||*J0+ciguHB&HbOi-k3Tqe^1hGq*SR@*?O?lZ8O%lB;Qz~cwg&)#iU2{rC_5w2}LI`9a zP=0BQ0a&;-;L_!^O^&leD9u(UlG=q8R1LvZU8dEpKk5_bG;^Mos7uy@VLsAAvKbo`>uaSME zw70+E_v2}Un8y6gsB!I>anbQ6JsCy!E}e_#G;|l|8^t?Evt9MgG#X`{vLK~#E%iUH ze*DnN8p)-R2eCmQmaSHXDl=1o6lj|*khS{6Dq0Wd+jlkff#vX;;qwb;4op(jlU1f- zvy333k1&(tMwa{7k+Qml)(W99`+wzRsE%n`ddA=+GscvLl1e9zJ)G_WAdo^6Y0yF; zR6uZC?4@j7&0d2NdFH&UDowX!>_c&VZ6QEg>yb6?NxOH7oXkk^6A4lv;=P`k$Nprs z@r}5VNAF3^av2|Yas4vGMNF85(`%Bwt(J;NDJw@sEL#Joqe~G>YHbb20Sb{Z^SUQA z4i?>`R2r>yV&oShHuiQ`PNUBmc>NL$aSh%;n9i6bquiB`<>WwM3s+miht$4`)z#!w z`rO})Cu(uI`igi)=F}G68CHaOEtJ4@CKd|Q&@zTZNrhC>2msRbm`3kq#se5YUqKSV zYa?*6GJmCz zCIe0yI@Vy{Tly5~xb#?^7LgGz-sjq-rfZXS>PITMB7`clY3Wn(bcW0f|BjtBcEGe5 z2dy+(7}+ds^DU$>^MXLSI-zdeOY59K+Z+vN#1e7+MvY4W)@T)=iIS)>7tL*XSRWhl zlZM#ufiY+)Wn3dlV;TV}fQTqEJzu1YCymTpqZC>jz9XG|O_3I^Kw{?ci(aMKktlPl zzri=lIJ2d;2UEdGZUBWq*v+k$+Vc9-`^>KwlKQTolo>JOs1rmAP?mcrkVHfp!`GES z>&TeBl%qOf`M3mS&LuK?3x#dcB_skDEMk-0g>~?G*&uxxzIDlrwR35c9n03(jiI&Ki7ILAh|Upe$6Z+6$~VB9HKwi3mH7VrC-d@X z5_GXFgb;Cg2DiWO&d3|~)!l*glt-Y#&ph?p)Z}aZIf+XS{em8l$aej5(q#R3CMYOP5T8MSL(2T^HL*~O*mDW zFf?bh0#!B|KqCneL@FZEMYPBfsL(j0lqQgZoE@uUK(*3lV|*GaUfaiZAhM{OM+GB5@ftwC#J z99CBSlR?~!C#L=W8idqSNWc~+2{gtzH>jOK#esd z+l*QK8FJ6@+W1d*5xm}~{l}`G#;F&NQb3?3f%XfQ8-uM>0ZN*`$-g9O*1#9>{y!U? zfr?{z1G^D)Mp*5Fhipi%)B-Icw2Y8S5@<{vHmOqv;Xs>OQE@bdFiNSC(o4m5{{}FC z0hEXDpgFy3MM_B^B|#7p1UYgtM=l7-hatH@AduvO96^{vNI|4UoI>^L`X~9xkN*{b zb*rA^uRrn*zWucy0Pu$g?&HHB`Vimw#V>dj`1Zg26QBO*CwO@ICaN%-)<^)NKo|zJ z9&6)g4?RgMP?W|Sos-6R7z8=0!WwcxNI_`p-8Zsm_3}r!^TGSs0Yp;Z+D!LXNd?%3 z3m_s*C<2Nd$9U+eWjwi|g&HXd!w?ZjW3UZE@5;M8LxYQhrWL&9BQ}p z9a{Pf4b28QqzsU@WJs6~2m-@TLTTwe$RUCpQV4r}fEEx0=Dm=D!2T0b8o41|U_*kw z!tbR_BG*D`jI+5gAP911>;gd~1Ys-_b4DIpqm;CzUxGl83v=Xx9FZ0X8DPYz;PA1% z+;QIzx#RJlao@5+ZBs}YGLRBb84??5=at@BeD4qW z!UwM7S3mzHpt$FrU-149y_dTdFJ>2tZ~g5j`ShQCjF&ez)7rY1FMQ!6{MFz54ToCW zK?sUU*{FK$86<7ht0XZe{+r!(4eY)Wn5807T3a4u)<`IgJ`;vj2x-<#B#o;>_LC7* zD$yMwV%0N`vhwb`0NC)#T0ZlEKjB}$aR(j1v%mcwAG+ykR;+rFwzh+O@)Li?r~mRZ z?COj-(t3z*{q4uN?NgtpwG~#cc#uzg@&kPT_S;dwvya}-|NO{%`0j5XW!{H<@6-o51LTN(M+9 z5R;RVoD}4wjCFS@O?0l2l_5D9B1AxIp}?m;_eE}a&&Rm)p_Sx;Tv&ya0Y|pI%$i4k zh!i=3AVAss2~owu#lK?7n&pImj!X%Ma!$Hat=$Ae>Kgygq1F~|``l-cLx%CyyEj1f zVBUJea4rZyY7_Z|mx|b1R4(I@7949mMxpr-^#mw!dY;~;yh&xEX3al=(6+O-O&M`z z5^Qh}2!R$hRaPdw`H26f(|4Rg=K9OqV0}QFXk8&pRGhXcTf%-Wgh|cTHXRJ@8J2~) z)>i))*4yCD6~Z0Ml#8%gN>x=k+Mf9}wU=Fl7W(L`>CCaSb)3Df)E^QZ)5eA z%^V9fI}h$-&C4$_Y*-W3xjegGeu|arcTi{=MSV_?L$GVlF8u-5af%U=wq)E1jjhCZwqTyyi8}qP;_lIHBm$$CEIsw=LcW;E_)fm z+4C=81e#DwQ_IU`9$C76?6JqV=9+8l&(?jG-Zk1f=qwe9fFKl*Yk(nxIJ#{eYhQYq zBSXe8tU6%l&dsddu${t=<*@xZhEJQu#@#zjJgT_IF2!jA^qwBOtWQqXvqPq~|hQpL=tqACwJYEQG({VLI1nk+nn-wdT zp@udwD4(Zg%cH!waW{JKXx8ss&5E77n0wU?REK$Ls|6c2Z(>_>8)Jty^56gVIo9s# zVAjlYcy;+A*00;bn6oA##6kA$T+Z$-YdF?0o*)-e119{@%5Nbh?AwM|ejoac??cET z{zJY_qjM%fn5bwoU^sne=fdxB={cid*eI4AIL5rmQ@Q_J|H9%;ZA`dsK5On-&lf&= zD}Q+M$J9WdYrvQ z!JuJ-IkIaB&pf$=$x~;r>hb^Px4--q<1W00>YP9*Q^rh?@DlyhZ;(yZ=oxRZ(;5qa z*Kcr707_|-QY%1rZfF0FwQSnDpTV`Q?AWlBz2QiP*W}s0;bB(2_%cI>jv!x^V`p0j z}!YirXjQzA!t8D;qXi3YU|nFe2ALbMzm6d!fuJRacFJWumPnM)2B~QjE}u+%ugm1 z8tGDZg`l&ugFVe5Z@m5rT6aIh-sZJzYdOT#xBLmu?rNf8)G*$Ao6gPUN+jFD8^+NZ+R+mw>%;2zYHyUAbuHgwe(yLY44(fg@}vqZSRfk@J^ z1rb|UKf(H)2azL2vwz((UR=3`+Q#wJ*M&sN#7LUV_k@=}@x&9%pFf{`KHn>Nd{5!8 z-wo>)@%?}QGQ(HtggxfBzTUd)HUF_{y7j>&>@v zxIJQ9ZV0Qlw9%BC!(|s#vHFSsAXnVQMf0y=e{l$}$ib{W+Q5Vh7cgP*_i%WB9mA^|dE?vP!ntSF)4B2<8uI~{UH&FI zMJ=0))hubwGid7doLk$**+*6~_1brG^_%AN-1EPtY0ONny6R2LnSCw?+D38SMROV2 z5E^>6ds1x!H+Jsa$@cBrx#W^d;y{MpO!JOfj73Z!l=;nCn@l;uQ5OH^t30q`3lnC| zp>x|)EPG)iW5!QHMmu@pi6^PA9nQu_e#XKl9_Q%T1vEFz;pO*u$P>cXQy7;7Bl-Xix(!JDWLlbT9WjzliF_(aan>5v3JEn(|=M?w=?eqxJXS zqxP2fqvdF`9Lm_p91n`3h(e)YextSNRN_xgMbZMo*x=B6=k&~kEMIy7-0=Xv%d;t? zkfaqJ_m`DrfVOwKa-|qlayodK-pk^(E15er=qnz!v~r@DDB zJ6Ar#8nKtNFTI*~zxz+A212EiMs0MXZuc?qploWh5CMhaULJqqUS3|ii?iFBI5d3* z&;I0DwjA8U(VDwB_f7MVje~i4^Hw&$(#EfM?q%PG<&5dr%H)r~nH4J@BiEc~>YO+5 zgCE^TUboTOxewXU!q0y88|oTnGj3WPfBxaiK}29Pf3T|=`rPoIM7Rh^S4$c|Gk)eA zmM?mYjXMr=$%k%a0}(q4HC%M{TiD(V9raUbm_3t*r>fXrC@>ZnMolKn}So!N8@!JQVrFQ&WCSUw!R9!t&#*b&|U3c?^Td!o_i_bH(z6m-y*tGDs zEL^dRgKY&=zKul>JkKpRy_>VfPe7qqxbQKaeEcacy?8nsHmqmi{sYuAgqC~mWXk(K z!LD(mdG5)l5p}OJ>Q6t+lKY-x^|~z#9W;iuFFwhlr*tuyfFFk%Q&4b3WCzqqDp~#@YlPFA>$-@huL^f_= z-qCsvZQjMq$pOzV`4w-x={lZ!?j_#&uFuiwIxi1w1iOIYEhgF=a=B_QxndqI2e+bE zk7UO5bLhPiNupw{Qd)YMUY< z#DD?!IN`LBxbd7xeIfG|hz>)Q0fzuxIB!-ub~>K?J5AS-(;x zjiGePwE2AY)1Rhu%d4m(&FnpJkaykmM%r4N37U5>aoTzO;?8@RJarZ_7|cVDJ;t7W zd$@e!NEYrm%#`^PICpd{TQ~iT36tjW=#sS*8qY%(^0YM!<@|ZWn9?dad}JrJ^`mGx zIGAe}TtFjGa2aUy{h?1GTm)$~ia>I~#TTNYL%j0h6AT+Vg557X!>#X_$M$ur z*|*~W!|D&Q=;;S|-?eX`cFtuq(n!@SJE*O%X40gw{PF&0VaIxIxL_`O_wD765B`BM zW2P~B;xrlvQ5`#(e%=(uj;;c0Y}Qi8b*TF)tc%}BT4{T^D5J-zVv$vAHu2E@-Hg8Q zGFZ5n4ZGV2WfisUtN8sN*N_`>E*oE1#PjQS@Rln+%!@C-NXx$EeD;o~jm$22rsRR0!Pgv!yko9D&%icw00TH9 zk&5^zQV0jL;FiDUq9Kp+&~JW2P2Kf8_3|zr+1JQr|MMSt*C1#u6!~9&{Ut_Bo6HwJ z`Y~#QW7HH5p@}#WbZ{6rk{>~#zLxF4v%hS3n5_v5tee}x}> z^)@D5`cdkS4&{?y`gaP|oA}pXK8Qw>A6(6l&cQU~1iALz%$Ylln?Lz){PMq-aCGru z?tJ9`qiXi0+;Hn#iE1J`X*+H%)06I^eFj?59!XlOLK>RJFeC`6-?Ep&GlzNirRUIa z)yJTD8MlAwL*y=fGar82O{`!25c{WUCQTg2!qwm5wlDn!qlR6;o_p?M|E~4Sf77MB zI;x$!U)shNV6v3tqM|9wty`Sgl%>@Q4I9Ow@D~2P8bP#Voby6X=_rlhngMj&$T*>=CbO9HRs$%HY zXSwF~JK1_5$M^18!{~FSvTVf$u9!cL=N3J}w5eAzXtYGVyn@zckI_^;i^VUj;{3N= z!MNG;nK|u30y{VpCrqMcRx_6^xRz@lKvG#y@*Id%nGpT~F-h+BbieSyxZsXAeHd<69Mzrk};&L7JLS zl5cC~{OO~){OZs1U(1TT&;pYh;mY|JaKnshij!vX-A5kdU^s}*sGZ*}S<0^5PzG<` z$&goHBENGV4YMv|3q#ntX&E!e4hA4dy3BO>^!-Y)P(ufs>PPa>_F6`^)iSDf2w|ayYKo@rHKysU%GAXi zD23cTROYoCG6@VCJe&#Vzk%w>Q@d(;@d`!`>MJ?nYv)t7MW=wU{keHrTy9H0mc9@a?hmXKYqY$woF zwA4Xs^>8*Gtzziz0u7C$88o<&=6wekT3gLfpfemwK~&KZ!AsjlGv|tV0EC#vpXFJI z-(0|-QCGpbcWFJ&xNH9En0F{4kU~-@ZsC`A{eko5e4I%$hSIcp2ex%ucA;*VOK<1#Zp?wWG=i3%#&Pb*KjDh)yO=OzC_n%Cda6cT%H;iM;!^+gZF~H+35ia?!}SyzyNZ@rUJqk0`d8XV4OKU`h{`l*l->jK25I z;u#KQ|4UcRbp`rW*~)sRgoEj3S@E7-yib^-DY~njw@jHv4AOSwmP#O$>y|7_JPXqP zm#+DwL7K4l0SsUOJ%MxJoJKZlYbgR|1Fj>2AP0@GUsO}98^PEKXLH^BDNL-|!|sQl zVfprb>?P#-cf6C!FS*p*r`XPc!_5pOVAA2W?EB+099wsUbB7M%58wYZhoU2NHa79Z znge{}sTNx5r%-j^FhOo8SKa(R&c9>|qWL*qeDrrLU$TzYj%uocYF=9SJkLI{f-U<) z^0mVV+m7+_=5@The;5K-7Z3T|CcTzm=A?T}O&3EJ_%5RvjX7!7E zxNr4F_SH2}EJn2Dj2y%30zF&9>NhRlH-we%g^`G-pRW~wZ$WX4h@nUKl_ON@| zuh{Uy680afr=u8gwCyPE#Uh7WI|&*Z$&G|!)JfFd!NJ1?7BAh+(dGiXA6vxAl`GL| zG*yvc+#tc|8pt0xgy_tE^8k8^IBUxOePzi~hSk+Eal%BNeCPol{mqa0`G5XBcYOVy z=-hCKDf<_3`1wWT+d4U?rk)`r#dJh(AAJSRXxGtM;LyQ#TB8DM_pM^Xl6zRTVhNLH zoyG8dkHez>huM<_KVP+uj%icae)uR6l4G@X)QuX=O;=sc?q90Hwv;#0| z#0c8k+j(&5N>(1~Wb@1GIkb5@x$3ilfLb(TbCS7NyqW4*bEq3upl)a*m%s5n1i4{6 zzj7x>0VS+)UBwR$P*ET5m2k9KXk&1FH7W|IYpkV4LUeF1VcS-=E&BsIw{D{4z*eRY zRqT518CE>9gf&}QX^UzYKWYf|p(Ie64rRhGqQWtn*Dqz|mZKc18clnlKzpH+>Z%;0 z^8qt2y_(6_Tup1?2_AUjM~poGCcgcIrfKe<0@#Lm-PhmkyRx;1P$n-jCqp_5Z9M+< z-!e@%QVjOcf>q96*%_32SZP5VRtXK-uX8OtR{f?2%irTt%_UjH7+;A(Ch74!M z`7_B+9>uY6B-7?xOWV#Jy!zn3v-{~EF=j*)n+``jwBiu;4O195d(e%^55 zR9X)1;Na0i9Ne>o@kd*kJ$wuSwylY)2io0p_i7$*>}t+trBJ5QhYDg|(b3k4z(VlwWBQ+#;MRAk2*b7)RVjSay|A; zgDcMe;EAgfsO;wpalt?w}7+;NPa26@eC67P3 zg7q6-X2h5#7F_vmR=%)~yMOn4M$MT`)A_TYu95oc(M%h64ugR}CCWiLHNjRe@SK?b z-`7a%{)MM%(ewZ=dv^S|*<5wSjcj{$BdsmlxbWPOjIM3w?mK?Mj%|mSG;A!hM~!Dv zLk)9BS2K5{VvvUFG1FnlY(@@0hh@uO=At>1*}41~e)7E^Fn8V!oIPpbIfptC>S|;2$SDPHZcJD*_4Ipo6{W{(-fxX>7Q>qm}bTW1YxHf}<- zwli(SL}rhhz|EJV=JG4AX698l z@!)f-Sif!!H_V$%9j=ysdIP4VMoS$>+NA@8R4iLb;?(*tEQ`}JN&OeVuHCy?^z<`S zJ41` zmh+fA>m0s#+ZVXz>IJkNJ<&ei&~B-uEw^7P_YY3HI@n|Q z1~!Q+a|T*P6g%^*d3hb*{lQmR^85o(A+BIeAS#+nVAIoK*$y=o)t z*1W{nAyteRGm}-X>|yJ+{Y)A)lvbO?{+2gi&YqTLHt#q{FmwvVhDqG{{T~sCV@$tf zI76m1LJr1_o6G2?F}RYuhzPA9LfFEr!WG%ekp9V~XfbK{c)fYwHg;}YM=*IRBR}{@ z7&MXZ{re0)lGB7LG6p~;kkU^QJoDi~&G+u#L+iTDjM6y@mC7cZ?C&Nq^7`{9yyG%; zUV2<(BX9PeB&BoXD`JCiQqvck0MyldB{g-s{1qheERb#(CDZ3AO%Y8)LoLl;y_KfF z`UGOmr|oZ4RLIL1z)3)%P#~AfamO8ZFk-|AuD||zqA2S1Mbpn{DB~fNGN5DSV>EyJ zi`kTP?$Wy$*8ob#!F;F4OXjtUUuylc`~ z7pAocM8yI@XwI{KZj$75zjP3Gyj>M!W%U5yAKng^%z_*K8C3)sSnG2IUVp;0p1tt2 zs~~pdy(S19g-)t+c@8!2W~@DNE24r(ifE? zv}0IQiohyHT4|&VXlrZZ_S3m+wmw6gP#Z-AK@cCyj&d<7f`%|ORpHt@3smO~|52)llp&zd%IKG- z8I8GD=l}fxRnr?$w|pBJVU$))ucLWKGo{54EUkGxRtgGosh{UKcY>sQhejYdaHyGI z|N5KctLu3G2mZ=LZUIr$j@AKTm`h);BZLSHKe7e@Ap)XE5rzQ|Jn#U8LV>rw^{w%< z_XfH@Rq)r7l1^R{cT#cn4*ljgk!{u}Jt7f2P4tsRso>Ldt4;sYMPQ(9Z+sL{(EP5;wB{S#mO;uoo_tLsy= z?x;>HBMS_VHu6wWq)_anDy)sqkCY;C@=zBEL?Sth7~ezSn*>s-PGm}7q&`%b4TTAQ zkx0-QRg93iklp|CAqG#aq59q5Kv8A%9qIN=UE4CwT#J-jqne1Kwb|v6wi$5})tMN6 zI||3B%GCgpNEHc$@9>NzbNoIfMZEo5WbOb2XdQt7sREQ#2rc5K>`Rv}<=JPS_EUj0wB(OmXH+B~817vtDsrJS!WR%S7vIR6rJwdKhS;l(W(1dZ9I%%911?9{jWq+%bp8wC*YLqd8C^rZD=+(A`{J9&L}0y z8lML+fB}Ht4$Pqv+bQQNd98@Wn`Qi_qp&l=f)J8?&M0_hgC{|l%{5vO1lsZeqYP`M zK^6%K&OvH(n($?)0RORyK zvml^q+ytggK8p)3o=ZJ}9aE!XYa0hD%pHvBN`@${kU|m);VI@CrKMjMVK_CztcZnTF z3Y3cN&FO&M-kYZDBi@W5K#G_j7#O~ytfMq1P1cXr8Yu#VmA5`iG+WCEr67RDKuGJ%a85bwv<>i<@-Q1MZLCy8c6nt@Swe`ILkGec z$xKX{bOde`FhseiXpiM2D=D42u1TtU_GS+CT9H+g%t4Kmnjpxd(I~5TI$czQ(L;q< zWBo&+w2%@JJDP-b?T1u_p&1LUkRl+E5?8GONfKV*jJtp_?pnENjJrvkcx2a2&N@n!odXva zU(^v&n7$d(g%YIpvE9K*jh<0-PaLk50vh@jtT64(C@09W?=_%$IG^R}Y$u-3uD+F| zd_=GH@(f@ArwT@3yX0Phv^8W)8TKhAEXdR_UAOMQmMTfahTmqfOHHIkd zZEqhezL|+dP)dLdO3pkXS}!NyUJrf6cI{Hi7EQ1l9SsZ}Je2EieoOL*S`lbLq@C&8 z@PNR+Hk+2BnLVAPiE3@O?bx%7T}~PW(HGX*X{zM0J$%>;a?^8&sV!T%|ZM zWL`UCikVG;Mx%AXuE8L_hqO(wv)2<}>sE|I(A&AvNki!dG+cKaM7HzKVGD4@uJ3kX44 zKBMDiZ>3s%DMh<)?+Rx&*YXW@t_G|j)|_@ujF(7lFeH@YED0QGThCJ z;aB#gc%h{4)3@l|jiZbFc8FqQt5zmzTF0_C3maRllUs6Rz@!>j6-y?3K%)>UeZ6rEnO|#uo{E2}P{u4ANtZT_EiVwEt&j?2h|tEE zppor>qkR-}t- zvMbBM;~i)cv6noF`7~zrR_XDg1$>;4`1L2+=~Sv&2Xr8~!* z7$sF*#!w57tzMTiTJf?W{|J31;Evn1}z|7y2M5-CSr66YxG zU6W`R;Txm9r|?4#dK}2-kvNw}vT$7Y?Y2A9xou~bkBz1|H%Oa%%F;I8c zhl+Vu#<(tx^GQhG#hr|#h%Y9kXY|!_b}`>{8-kdpSVK|#%0O7XBB97+-(m|ar0#2s z>1Kt9jM+8jSWMLF$v`(V!g6WlQFdAqq|5!7JQ#)V zHg%yIX=g@yp!*)pr^nqu+U%gn?0K*}S^?5#r6ZA2*-cH5x|8pNRPxKmTGYI3%svaPTP42y`s9T6Y7$S}XWKO+^j7=gv_^Q&#*`D7k zG5qTI%*24JGJh-F-!~diVN0XUeS@sFoqQ|F?#OBBQTo&Y&E%v*%3dFwe#W4jSg3@` z=eZ5CKwIBMD}5Z5|$2KoT$QAWl<v4o+D9ofasCSEZk?s*-o9~}PRFxO zP6_OeQrYh3(lX$LP4eYuojX_Byb(S;u8>hGh1FoK(iHdjgh*dtFcfcQz?`GinH3&=yzfPl1Uksaz7czlD!$ zC8k(KLY-2&sFxsK(`gfcLJ^P@f-%vK5fK3aNeP!o1ePvQ! zA(6BSZ}dHq^^1Sl`QH2D^TN_!%I-H4coyW&8;94WUBP6xWw*X>{T zDoAkylj*3uv5@vG1K%JXTUk6ALVUJ=2mk!^EBDSb7`w7#eF|AlZObA7$=E9I3@aJ6 z9o@2xsU(4N#@dAH5}B$=@0`;#?$4+T&(K7mwfB^ieVuxW$TbY_q0TV@S-pLs>?`D1qi@;AIH=37=Qo%Bf#PFn0sXB^rhFnaGmP@!0Q19H*9?{)6sQd!+_Wr${ z=2H%_GDK)<;o5qc_Eh(i-G)AWu^ZDQYR-&sqKG73YE@DF_Mgv1csL`PBtj{l>e95H zrO)g=-P5M7_6XShKH;5Ea{g(6siT>^23#0G%MK5hvO(EZZ(n-@YlE- zsU+^-=QJ)oJ$+aIb($N;$2?s!t5?SIrI*XrQ-w5dPdK{U&}Sv1&%+TSk-M%ek2m~| z-phTfKTEHNJbb<3>k=Zvc|;fds_bZH#<;K3dR9U@Z7`i9g#9c{*-m2W%zbR$U#y$z z)C_s|7Rl@c@m?z7P9n31$Ud<f?5)GW|{68Fzu> zZh+S>47^$hw9HY|`5xD(J$YYlit(4V771)B3Ec_&LN=igU@s_-*aA|)i3+_6# z`psP+y_xUXFjSr;KaMX2<;}INjp;<2KoH1u>X8qB(@^Hl@j0ohGdhya{J6^BcJh#Z z&hl|@S*6UxtGJk#?vR}=cddo?OgCDAFy4QKQO+Z*9#Q5wDu0LnU40Q*6Ip_;>BTDk zsx*=-y%ExJ%IWI?o!K}zR9X*N*_Gwd<iD+=%0=OM zzpt0`dAGgP%0}?S|0OFpOS6^cReP^x<};`?RNb$8)3Mu&4eLodV!uH-9jW^n%j%M* zV1|kMb}L|X9ytjqXPgh6(U*fB-K`SJ z%j?)+DEfoFz!|UnADMZtRa%kOkjj=kZY0;~IQT=OA{EDoB_TH_Hv=Ov@Se%*<251- zLdJ>~^2JWdSX~TG6;W%tuN3}T?sL3G-R`dM>9dy8*%c58h-w+8+YrTEe5&hUI)Do3 z`3}^auG5TI+Qvpo*+xg+vHq&^y%v-+l6mo}@rb%HE{Ym<=f*Lb!DwNdT=menlU+*X zL{Pcs)7_k(_*5!AN9l1_n=k!D$@z@?@^5<}F7**jIs=;KJ^oYmHGOv1Y zdKZqzQ;n`Ri(%%RVwQsGLO6WP=*x9qg z5(J41)jB}ytZaRKV0eGe=dO~|rzf(eGRoaiRybZ6Dc`?zkzt7(I-DCpQ#i^Renp>1 zp^oLUbey(y1*aUzl%FF(*HJo^N2>B1Ioj4*ptM^L{ew@O7!o0s?YPhtoKw_wTz7`s z4w769%6$yaOY?dFtwUS8y1&u9>{3Q_bv(<1x{^z& z?+~U>K>!>OL#YdgQ$^kwGHuDXo=1kI`R41fF0y;FEXo^jyVlvAq21qKojCmclXZwh zk|?s8zHr4J`U6+#LVwG87DRgNUA$tsAh7Nr;@(v`c7$UXAqIhUE)~0esuMyO`)&Fl zFLMzW8`iC5_b>j7S?VyIGNfdCtWGUS>oTtZkfKZqLisC8-o57es_=^y5I5TJ&aKo1 zPN#pLK$6SnQ2Vztz5OVy&HFf93}jqyE8SE4s-=8zDQmur5HST2-Bxer>3Tc zqeqX?+S+=m9=1C>9GR`=NUX6Y*zgtdzP0Aar{5Ym)U;jv9qs zkIC6R;W3bwUlP-Ep$mGiP(REaPD>XP*&0^Wz{QurkdVVIEwr_FVj2|oZn{m&Qpi$d z_7fwQ%Q$gGW!|SDRmv@ma=M4PlhTU%`g+EW9gB_>hYz;UQRuV|yuKk@PYiBtm`ts5 zvFu?#IkGfp1%7dVZ=ajSAVL!~8P zbPW;n#;WQX7A&}w*49=!I@*b%lM$J9CZha^XyH0FB<4gne&zUxA6HRsUy;2gD~E&- zL`B7*A;TCn|8i;u2ecjB$H795Kom$vj&+4nUUq!?@5w=T`g*x%GS_seQKpV2`@BlO zuB)s_e~dJBb#*ijZsel>pS?Gav#PrCzQ22)bH}ilXM>&gbm? z{ISou_uN}mfDsf5*5?CNb?+JXUVH7eerq~+E}2Z0uFh`kknLsW4FwrL*gG6bw<#T~ zq5yb6hri8Q#Rf(#CmJrvP1l={`g8O+D$!E@TwW(ir))6ehMSP`#<}^*8QRGSo}?CNC?3OtCuH+?cu)a z47HurDB<7=jio{8{&m~}CfZ@W=g^@3V{R8d(q&HRAeDY9F#Dtr1>4F;Ra;Sq1XA~ z(T2V}IyeU!kQbsB{NCD6d`y@dD|5s;C9c$Hp}qN&hr!JKWz)MUseGN;-|32$kN=w- z&_Q=__rt+3n!(=gU<_LvBds)+CCH?cw70im+Y(`c5JRcS;K74n4$dK^{3Jv%WJ7vi z1p!{a|J0$$;0621@WW^$U&3LO76!yQ3bLlC_m)e>AwlNtYHDg3HEjmTxWdu{kD{dp z4KO%@Ptu zro9~kYk+YY&;?+e16q^IffWkT)7g$h5sOA~av5xENNOlYnL-T6sMZXIuG*6=gysN?sUa2xTFJILO!yMg8v2rNL&YJmOj6Dwjwoj8-lktp^C( zl8Sf~TfW!MQ{Tc+MTc-9Be5uUE(;;b_MYR5v@Yeepy)ELQt>wGApW`QdSG}N1dihnjYdiL^^(uKB;qlI z_M)bT$liKnXkfU;OMWi=AKXvN9d{<@(G&~FD@`mGBah3gnf+WPLd=SUNa7S~tQ$Q{ zprk-43n6sBdlzf@o)~1tRuuMOzk&Fn$09IxN)w5OIk3N(uoNU>VRDujase_y_gyel z!0$8FU-rD+*usLF;V_OAxH$*Qw%C$v#j?GUkW2-~s`miBR6QSmJXa#{3$$H83XzTNMWP3c@0_^F@%U)(JHRpBZ{Oh)Mt zHj6tOxf{hubWvCx;r-Pff`PmJD4)xb?&&6*S6Bnsp8{}>L7pxG&OQq{#!Goj)=Tz` zQspAu7&6&MgdCRRfYK(i?wEY;V}OL9*flC(xRoB%?=;`PCq%I>ogE5!bptiZb#Vsh zka?2+eDX)rE&Q!ODMCV05el2wQw>`AP%wbN@qRm;B{1lWsbYkND-M0J!M-kkhmy}d zK6J}J*9P1xD1|~HumTMg%K5YZ1FAr9x)4wplt3!h+jWFVDTzd)XsuC7p_DtM1z!eOG)LgS*9Mmf#^@kyo6F(A*CJ;h38iDK*2jgWE@dN_m?lA zeCJj;WE(l-x}L0gj~=u@rYwU0%*Cw#qqP!zE979;e|sxfpbMF40`m?^QfRh@C5TvI zY;7StEL%J3ZOMpZk%A)_6vMu${$7&lpstXXWsF&m-_4ir<&zyBR6a!Iy$MQqSNP4< zOqn(9f+a#ELLn?%l&eufp_B;{@ON*mg|Vy3?)A?1qT`Q_#J()0L@7-;Z2HDH&aoon zk35yfg}Q_MZs^aENPb@O;QG8zN||fNrH$p)fOVC|QL5O`nZw)B{NgFvvonMM%PC!d z{_{8kUg6KD@-;pX_1AqRNM&^9xBWnVxB&$B<-zVR%6?ZKuxwctp>Wt-Q8{MsJ<@~o z9+IC6I`Fvk=gz^jmx1={!S?VXmCm@YIhja#MP(@Dx!1MF3rAj8w+o2>5VixP6j)XW zDf37n(E-6@QbYyPi;(jJDc>tn!aRePen|!&`!`-2p%oI*|5=0KUhhc14_@$4RO%-+ z&n}Rb1nC!THxYaSPOzf&he1l2(iOh@)E`Uf4&cohz$@yVueC1Jn^u$}9YP57kV(0C zx2Tl&3(>3!Ea~yQUv^Lxk8rdPxB~*LKy-yt7E&tIL0ohPt$e7AH`?#XB7|`O`EskA z28_>9DzFC43n=f!q|b-ie-i(gz#~{9AUQQCH^8GFfBHO)7f#{bPby#eGGijWG7!oW zVnz8Yy=0O=bcFy{iqdxtXx;^g+2`{UTFw-stfi+oC_nsL>VkLB#Vc)4kTZdZ(LlZR zQpd9L8rlhv0=M8DHmbFj#^|)9?8n3XW<-H2OTSZr?+)e3F7QIZ%jG@RAnV~BlLG57 zy2z^_&wK6> z4D@J25Stv3tbz<1h^GxHCCl`0Rgm!o=S5%`g&dl?q%7zcWnTpJKvj?#0iAHbd_Z%e z_PIZAUI&?^Rtgj^! zi3%vNmR2STFKE#ot;e8Ts3`Awf`)1Nxd#{9hrKp*;HR&HmRzT3pqHkB7QtOr zFvL|M^^d|DE+%W&+nUIAk!rz(@v9Csa~;SrWn+>B_WvGY@Hb+E&njEKTP% z9!}HH&GL+$;&Db^mXBBYn4Spyt^#8b{JS`};E%*WX^Mk6uzV`M1rRWJlnKF-3R5!3{2M4f>3HIC-&h=Med>TAU{q{>UzLSXlu8!l zQ4#SlvO^$ILV@;YN};6;;KSye;@p?_yFZj+S-R((Bp5(gprrQJl(#8Kg;HitWD)H( zBdH}?xTfTm7YAl}sFZ;wEOVsNxKf~mL~9FFY5r6R1@_M)2NfK9i?j52I)pgmm5Aok^JL`6rt0v?tD# z?#I9;lC0FIIvNz*aQ!HTAZTiSnxp(q<3-E02;3BzrpQiy`xh;Xy1tQ(UTxeGZZ8LS zeI+o!?!G}#7>~h@^%OEz_hPOTR8@;tF8%30kN;Ue z;P-F`!6PpL=djcG{v7g0pr|y1AGhMiTo|BM7b4;X(gR^2h-Md8s5gVskBBtiYbfdi z{yP?|FY{R;k}_bf6y}cdF+7+=Iq_&P`B9YguFhZyG^ylmZFbIzbmGXDNkx{VY{JmKqRH=L}WZo4TE1UI}7k=DV zc~DPrH5jLAu-4|mA@}2OnH)CxYr@w`e4Exs3yb=sE*uw}w_xn$`wX%7T5^ma8E z;hhwWi5%!!M`~>hq~jJpDb+Ys@R!mkJW#>VL!*N$f2_R@7G~uacaMU;jAG2CDFM8;}8|v=sE%4wz1~@EP#M5t7VbDi=i7vzb{G@ zx99-Ddl(@oFOOR)rKGoX-Wkek%M+>PCK{wWfO6#nlt)q|yb!Y`h7&SpLB; z>?eKCzYc5;=6`EIaQ7q~n)+`51CdZ(WRdo~`@Xlx19yWZ8cx!YB+mg8*mTQ1cR_x$C@F&kf$OD23A8eBiG?nt2U}18tBPO+1+S>^6>h8|h~R%8IPOHD{*Zl< zeD&Hl6aA`ZvV=QDfp7dsy~6bZ$guF0NCikgnM)R;$_7>mHNpdV{pS$@7-ujFa_K-# z!43z+4=P`U>-Jj@N18OnI9|((cyWu$v{Fbkj7Iy(j@595UJR?FBbQHkoNo9bq4p>>j1KWZXNDhn30|M3$ zwh_`V;nDxFqfNmq>4JPv7!XF{jiL6&QzE1eSeRbKzwn~VrADIBvM9l|Ff$@NIpXKh z0A6s8@N^o1@PZGdWPts5D3SmE{@9x=@}TVWONJV|#%qDDyuz&lqz=x5@3NK%>EU@k zCa)B(_VgL0u>3NIp1tIH1riD+WCFU>U=++VI1&*(65Z*fS1(xDvt7sdJ9ycL5DFaJS9UJuW<0@ z#^BQ^M}ciLf}Ggs(GJR0*g_DJCWceF#yXK!@e%w>YNgQHuSx7jhX#kkL}`fv zc&pJ$( z*jJttPosq&UndM~SggB&j|o|-2=Pf4Lz#W+U-xL+y8#D-H!LQ# zUl?88^%Oo6V`V@t?Yd@BNQ?>Nmy7avSSw#g5TLE%8dR#}rUQBMU5rBLgZqQi;f&}| zCf`c*_DmGHyeTmdvMe)iuEMgcflHGfBUJKO%99MrH|8vaGST#|ln7z;!k|OUMBD}< z|CBWK=3E071`Q!CU8K+`DLk2?5nk$|0)!Sw&$$2!;eqCbNK%QB=>-VbYzTfcM9UzI z6l#uo!2}X;z33}T0Ln{I$3iF*;dQ(=kSeZ1Rb1!zwLu41q0%_+r5PL)zTKb^mS3yY zht~zlb&*1NWn%nhqn?x&o^B(gw@(xUS_%=uMm0EA+5ea#BaZ#uSsA-t*eFNix(aCt zgeT4wt~ZCm=rmGe*%IM-UbIx$uHTy-d{ACe#DauYM#f7M@S#tFyFgG6Fg7$ubf5=- zuc6++As!)|;v=3OAoeDX^^&mTehFws(D384%v|urab7B~e-#=d1Kf?D#ge zDHB@^2rDHYg*;Mfs9n6w(4F){5807l>cN^EQb0HyLE2%I>rh!yK{OJ@aUJ@SDPqwW zmM!V*>>`!TAcUl%B8~!_e4g4w4OS>bZ?X?51c^iyxm=E9s*hMKPDLz6E}Npew+ma? z#3B(gnH<{9Q&m|-q9Q?WZ!bteRb?f~bc&wtUbM&)3Wte=yrYdb&HvW^yL5AY_vWhjFpUq|y|$LJ0gkNry+W>}T1xfz7R^wgx}OCe_!C)Z!$# zdxWU~m>V}9#sHPXDbFSe^Z$Ai{ruhAN=CK8Dd3QLli9LmXi zz^?Y913g8^tN9~5BjI40!Xf$iZ60&51Iqb-(DiTxD2TU$zizy`#jZjMNhBI3Y==mt zleo$y8i^5#MCj{F;W{o#7-bx#%sTKCZ5JOH^NYIpfQ#=48GK(=1!vNsUi`$~CyZZ6 zqO~F%3R78K0YE;NArubb*8)!^d%VceV^z#}i%=FDWa20<+rgl(S{R3#(AuPj3-2=z z@H3q2Ion)KELMTECFx`uS8LENxVn(utZ+=mnJ#7v_=bq^_6!#<^&jm83>dz23)yFs z45;h42q_TCj}0@(RUy7N=(*`9aQp&i#}^eN28%0J6_A%Ah^rJxVd8Fuq7Vfavcklo zQBwH~`FzGiePJV{iBzkKRgiNPnRLdCpY~*fH`Pu+UktWvC}z)aqKk>cBfngZG<$3$ z5+xk6$>&@`u`2xBn%>?X!nPz5t0tRw5K0lLjsuX+_L9%#5LzIFGqFqzW%S7e z*?VgaiqT~6);k}g6wJAb1$_VjAOJ~3K~&`N^`c-hqpOV*38iqnlp+sP$SN)e6@H{azmglvf=!T@B` zeMDk$d`v*QIfQ370OYbM^4Y96=8_^zW~h*>;RnN)IWP{TFZ!~~ClPq>fsQ}gRL+}4 z=chu55LHz*__Zjq=_KJu3?a=}_9lCXghE8BW8O)55Fr5anIx8NA?>I+H`_-%Y+(s= zY$}~35se{*nTK?;7sqiu5OSalnxRHPtj~8rn|)AtHjzRj{Y-d#Cy-}J34zucOG_e= zNP*wMrLVWwG~&hBPVul!DxJk~6}F(z+aysDN4XZ+Y!;-68TR#~BaxL<+{X`&0ofd)bDJgV$@{jz5o5Z_0sdS?`z>xpZYS@ zqbrG7?d*Nyc|^RLadR%=v0pvT&c+rlz4>NN&2{qduYXD1j5#d$zzzK2<>y$u=vAhi zJ%?Lwy^X5&7GBxBf$`^F%7ofpUVr0do__LWZoTVnvY9lG|KvfgfB(mrf9W-ZV;wjx z+u6Qh6(i5NgvWlrj5Vt_a={hXbLs4vy!?xw(Acz(58ZnYl@rF0&*u?(LSspxHsMj|YJ`8EFV%%7M)Zw@!#bQ9@J`eX#}5vGCxh5{v! zQWK6j)Ngo`zOD|Y%(((Y6s??sbS_cC-;<|?pg%eYa#_kX`jWH=*-@`Mt{@bWmtS3uHT^Q?jIs%5`n<}likhln9BA6ZD;sxl{$-a@n@eHm(}<7_ zp4UGb4w1`d$ma7XR}zoKX>aS~NB94ds_J2U=)-p~a#RJmbRU)d)`dOJ9%`bk9a(ez#^T=;<^r6nFw&}1nJ3IPQjpYtp$;A2rC>T zpUsBnw1H5h6!#Z;s!;wv!1n zW;1(40+G*>&A2En2!(oi^|i;Top1qXo;ICqvI}8J6NRTubh>5Pq|+%}S5aAE92|Yg zB%T}iFfsv7Z1VZd07(+%x;Xw(#UD zP1Mb}h?$AKeCuo9M#aYR7oYiC6444|Duoc>+ODa|D~j^wVESaqvmDKrK88v88Af=8 z2LxEsGAZv$U`a{d1xwmQA~u#~k;!Dp=ko>GUd)CX+T^zbuq4D|4%;@qMd!XAF1Y4$ zwr*?Shd=lkXU)2Zd+xZNrN8?(e)G)BTzd0Ix%Ji?_~8Q&uzc-iKL3sHF~$z@!gsz( zOM5r}@RcvHW7i6P{>XDoo^=s7ee^C~eRVktpZY!XE}O^PsS|kOe}2OD)^3JPp3BW2 z`xK{ES77IoC4H=XD*RoL_lM8n!Szt`-iLjKqemJO!0DGt(r9cU*s$~^e*4?saQci{ z+@n1Z~r3)_O`uAUjW%b~?uICau z5kS8(GO4(PLLnmID9Ml{pLeNH5Vi&BbOsQ_;&B|EB_z@y!*q3J_~O6*7Y%#cxa;=Y z`M@>vkSdF1+jQ($MqA?sCe6E(NUQ=U)r}pF6OQJ1{qjLK`Snt49;D>u;5+C*n}7q7g$ zh~NC|hqUe4NhBP?)h=j(DSUD+x!U z$CU9oG{|@Dn@F!n*yhcZZ}5X}eu;*a&mwUGF5>>`w-SD2pC0i0qeDT}#_)rQip8X0 z3Q68|X>9GHH`7B`$6mH<-bl*H6RE1Axw(anTej1i*W|T}5R%;uJNfpv|2NOR_&WrW zmliMMyt6ML+qIu%OJCyhtFPkLo8M34fll6RRQ&WuKj4?Y{v(?*633Rfc?VZ%61Jp$ z?=IG@U(edcHW~$VTQOXrk=Y(9L<*hiq<-rbl4%#qu0$(E-pR0b?J7o3o`Ge>Sohi@ zw8{qnm!Z%1ULqSxX~Ll}WV_kAVKv>|J!JYi*tm8%EiFyRh(mW@D~(P2$SFzFfi^l0 zG!qL;cI?=~#trLC3VK-5msf1r-as@QW&8SNJpcGl*tzcjZ|zI5a#xz(o^E0x*tv5H zTeoc>lg}W+#^e_o6ADhE~QQy!=w7QmTZ=Uw%Hab&zHf-9$u7;g-=b_aB z6|KZg=de^U!ndfs`0h>(qoNxwT*$Oj{&RJh83gNaC#WNSOScJ8DvmnKP`l$%Ej3$g+?nWTQ}8usq0 zClXRf-A7w@3yu3*(V<$xR+zTtovd2>I_vlDBoD0JwvFw(8nHqVVmgb{-Ad!`24tiH z9f`4fPZI~4_L0qYA%!3u4&x%&xP2$Pn|AT)(uF+s;!--&4%v<_x_UC?bc8J%x3aIf zg=n;bzOGJIzPXHOIK;-c7V(RpJwPgNgB3<7VQLB&qFN2P18k?q9kAtjii8lTOq!V1 z*ttHOOgGAM=}BkUQonVBL4W&MjH3+;^CivhuwSj^Tv)o8XefZrzR z(Ksa`k0)tRGW)TgQKL=sBC@>tr$<@!!XxxFt)p@0TFyCt4zI1=&1+k_`TcJnX797Gv6dlm7GJ>@0flo$L8{yY(>9MrI+jq5n% zl^{o$*0e%cHsn(5-@TK~Ygdy?riex&UZ$Q5^qm;mWZqIEEy0?#%lO{czryZSuj8hB zS-oi+XPtKi$sWal&6}7Y5AgZF_z2freJwk-tf0GV4;Ng14KJ_V!z-(I6N`*u#_6Z= z)USTTu1&9V;knaDXS2MvuAZ%}JxrKBo2DHb=-#)LJ8!y<>o2_k*UclVE691zBYc_T z<*6In2}HjFJcq~O!SxXQclcL0nxsKnXf3=7NfO7&)7RO~b(dX8j1;fE@-j=_Si()W z-%dlfV)6D2i(hQ4*lQIf9q7B;TmfF&eRkzvDzRdjZCvFf!K_|xN$(A1uya{QUx`}hCMndi+Tn{(N= zel4vn2Z*Md>D%@)ySA>Uqa%fkjv&*WrMF{0Z*5x4j@CW6wuREBRVS&)B+ zh~O=_6^1MssSAY#^x4!y24aiXVDR!!}&L!OVgVFW?Qm_jy{d<)r>l00%Il) z9to=#ZEG0K?Ku?O2_wP6z@ADAH20~ zi3}o=>Trw_eO*UebhSenO8_&yFB~iD?GGt1E0Kg3d#K&*tDUK1vh_;NoSnNPrm)% zbmby^@{9k-yh+snL?+sN_A`IM!@v18?YShoD`s%zJ(r*tJwScy4n~KsBAsLcWPr(;*t;i|MLo7t z6j(aR+Jz7D#Ph4U?qgqJd}KGzJ^fqUnz78kY#L8I@ht6~Ra8%yO<#K#(?e-K^SOI@ z^R=gWedT%$>!EpvZHpg@X_B8mZSLe=cn?UZ~Y7>ljrP@ z-^J?-UnHOG#6IgXq9aFg*_;_P{qjk!zxE>Jgi}zu2<;Y06zU(J6#s`oo`@6@r0n8_ z7azfKhVkO>pJ&>qKE>(h&1Oei2enm`h{tM8^Ahjhd!#n@qhe->MB@^R$7pS6;Nk!H zcbfAl@}~@E>Q$F9qH+YYMoy*qwR+B)9A)wI575;GciekBudZFl!q*#6@mXAZ^WQU} zV-2r9^#JwRPNuy7CSF{>jNg9$r`RdU|M}dfiS7LZFTM0KE89nM$KQV)9g6bDAAFZY zxPx&MMsUx4U!fwNVDXa0{PyLSId?`K?K{`;MpKMX(;iYUKSS;H_fmVtR2KjGX=+B+ z@rk?dV(F7lv267!!s^4UUi37Ny|9MU=6;-WrVacIyN`7^P$u07}>OueVq;bY0<09yXabOxaV#%LZGzb z1i^O!6V2zkaQgJix$oW{Dq6OZ?LI(5PcOIL`%$t>Uq;qHPJ2ru9r+1NxoR{`t2c4c z>`5%R_JjP>_ZA{2&0)&Tf6dI=dY*XXo6MSe4Q&lwR93{uBs+12)p5lwR}+2apK&tH zjK2Ky%xHdyITxMIu!u`vZ>ppzc%kO|yB~vh^vTP%k$ztcX`0jd&A8`YdlMnyu=L@d zFd{LW+0*OTzjqHKqG7_}Fi$LMVOU7f``qssd)jAd>W0xHPG?HpX^fvVmc)#iJoWSo z^meo}rcZL?-JjysHLX0ixdWMqarehBBJ<=|2{k=c$)Q%^;;zc$#H1oa>Tuo))Y94;#b>4U9=UMmE6Rh0T#?4>3hKwuO zw`UVmCeCN$@;6!Z_%9eY=K^k;TgCPryI8W~KZ%aJit9gcAFF@y6KW?4_EnE&bhO4~ z#As6k|J@Ax@~N0}DTnT~OQ2Z5@!h(qG_@wRiye*;uNr~S9poKFM_Tjz@)qXLpNDXB z>}=V?maSV^Fl+{?RXaE{KAws5#*)J2Ku?}e{L`1|+|om4Lp|}xC@dWU-A&kzF?rG? z_PoBF1J7x$dH*ep7&R6pO`Q;@vxAS_eiQMt@8J8-uHwwe4jXoEB}yG5_w~@TtCj06 zxRN#7b`Yr^$FRB{c5dB3<_ViKC!Wd9-Z8jrXYzQ>?0F05bs`8$kVr)6>++P6f?yT{ zR*gPsfc|+@?6wZBlOs)xEHFwqpZymT zXJ1EktK>6(`#qW>DZcyc5>sn8QAspcMU~9s^ffVc!btw<%MbCB2bZ#QLl4it`2dw; z$FsXH#>jA(F)~c03-VY&THX~Z?p3Bcu_RJEnpyKNX6Eea4C~cQ89$CU_O{T}R>8bd3eb2;t6Q>2>LaL&|mtX{u`t^4+H=IJw`ZW{mc$-6l4$8U1$`_5&? z`R5>}oXw^uUgry6{vy`GeEd(Ph3+cFpdd;F= zBStWB#2C(-eg^e1*f)9@4Q**^dtls{DSYhnH}cAk*I2r@l^*gee&Z<|oh8$oX7Q5E z2r-;{KJo!-&mBSUt~!>!*+q5jL@GlGx;Hl>$DW27J&}-5C?N`*P}E<{PmAI#_}`v1 z{0Aq2#C21&_qOuLPaorfA3aNU=RUd)RMXhDA2~cvDr}-GBVO|j_ZZP&3SfrNXxAlV zOEx#Oa^7`+$%5<8|&cEt)>o9anrx>#`SHPns0 zjx|fSQ8juPpZnVVtl0J(ukP8x*7{z4@Z;~Jb~e(v>LpUmtGVK$xy&7ZBQMssA#3Wm z`>X%Sn7$<}eex$LZ8BP_Dq}1-a|)MTaT(3$T)^z=sf=yyBDZZ9^RB&ufBWt4aBFJW z-q^%Ti~4pTf9{)%&uea&ousNF!@OIs^ac4VAwL@px zqBb7o`GwE(u}|DfHk0*&1&$4VKbA=Wp-`A)I>pXC4)d<~2Pogz#!7DW+8i71X% zP%(l~xB{fY8Z(ve{@~|q&y6NK<+{KHbs^5m(7?Q2(pdDQp!Z0EFnuP3f{``D*|M*P z_MT?uRP@li&%v&q&ZJYvF?_>1TAR0!?(4z|SK>N(l;5misFSBsYUN@@s;I51Mm460 zC#o4;5#f7Z{Wog%?c$CFvk0FtlKS;q*!SQ!nSb7yJoe}xcyZOY*xfOn+DR2i4T(se zP^tmDbsK#x8LUxr>CDy8W>t`uFk0v7$Yts1?&H}Vn%i!>0GUe@@@yyJXMuSL`!Q)w zy+bMl3`ws+qLLsjT;&pugt0P79{t_#7&UGxQ!kjw?;rgYoA#xtNlc=4e>aoIUdR`| zb_@6a$0ymn_W)ZPnrPbJ$o?!Oi16|s9z%*SS6+8HtDjoOxBvG))2J%Yldr*wkE0i; z6*iH)V|uKdNF+r;A(oy;%yku2HKVxfnj1N5+-ySe(Ja}xkCEfYvS{g2uADo9bLL;l zobzUJLEW8P+|68h%-<`7?wWARtiQHBv>_l;b!!*)-W~5?eSlx3thA zXCO|woX*}fXJ2+P3$A@1Ew8R+=ab8L^?#n{@t^*ZrLVk6PcFfx=2qHLZJbi0sLAf7 zbIY4Jojr`JtmVoJFXZ;yZYCNXMMql{?b_rrNtDhIs;Xv7<8B%b?4deVLo8m+2mk6X z5uHieUs}b=M;5a1p~rasvB%l6Z66D+x{e#)e>I}RX8rP3)Udf+c*T6yZF!xY?I}`O zP4eDWpn(GEKsg#| zl2uLZG$gac-7e0Yd>W(2j-?MxD(BMO(?e~dnnZ602bQ*wZs}s;@DZGT%1GXS!}WA# zEH*Yn-gU_JrLglbJOXD;I)%E?Q`psB!}_g zXldO`U&n6hLJrf%Ph|9j8B~lrpPg&lS+}}}e0&bk#7L9og*ZrayP-n91t-ud51|;t zr_HBRhj{h17kT!{KjLI;gba~(!AcCHa@Z)UYtLXr^=J~ZiDio(W!t7TMC}-9SJSH{ zqo&OyQ3bWmcJjJ~{hd3>wr*qW_z8q-DygmLV8gN}*|m8CArT?11Zn)5m=vDrM71tm z*QFwnVD1^y5PSFX){D=v^yL?@glSBta*%Zsr2De$*u8_416enTo8OPtIcA)7Iv32H zMpA{S8#jUtFa46{ZEp~b#EC^?jCa#G%NH~DqWQE|$8mKQAr+QVUV_&EWnl<5zB)$5 z(tAy+_=`Z2s2;_N_I4t6j$G4rHoeuuy`TINEuCA~vVSjGJIs-2gmL7lm~AJtrX>X{ zJ3{Q~Nn(jCZa&YxJ$u>Ru!BAA9lWw+HCq~bkntEO=%_$Skpspgp@Q(d7b!*nN zDZ0DbP`UkdwKuSDS3P}uH!v)>n^-8s*wdzBSH#E>rlBLxvOV2&rTR#SKBQ6z+hY3E zGZ^c*Y+g~%?(Q1)b);$T&12V0!mfaP-yX6NIA{L()Ygq56|Tar9>JVTFCvqO)6lYu zoSPve^Tqen7D9RDV6>zR0Xr_qG&wR6RAVPbcxlaxY>Ulgd%BubX9xDKRWvSLNOfWu znN*Z~F2wG}4tf>r?QJ93R*z1%lFTN_+7TvBIhC`gPbC~NSw;E0ChZ6g?A?vEe=`vk zWmMg1?Ai7fD;NKXVYRj7l0D>fgnYb)u~X0FoD0sybutK{3uS0d(jerO*w9*m&a-Lr zHh%ca*ZJN9uknkY|B+>Tv#jjM@y42Uv_~Ylx*8mn!AbX#QN7e%bOUv>E+L(4WB%Mz ziGpcFr7bKiv9#s!c`(5|Kz`)z@xK&~5EaX%2_bP@#n|zuvaew`>D?PSU#w^Mjx}tI zOyZFxYsp5VrzXWsBv zeC$(q(bBY?X~FQ$3a2-1$%N>Z3IH6|u(=#IbN1W^G>NrX@cR}qpHD(~|0 zqyK}O&yqNI0ehP_vts4b-0<-`8D_`GZ`=iadGc*Z>~NThNR*G=b_b(RsbyW$ex6ye zgY6CbIOB{{30H=gc=M$a_>;|Uccg9q|jC~2XT zWvVag2(64_XL|?xx;x0mMo~4gjteebz$xckOS@z9+MBP?l53}>t)5J4BNttHC13sW zhdK9*;Usf;oXRnrdCqK3nK%I%j?vW8MC-oo#GH2Q#26|nM-uC4KxUJGX{=US-0oc* zf_M3T?+qOTd~Jt9j2sn0$X@z-+L<&hN~Efb#)ci-|Be6PH@|#vcLH# z=f7_rGiOibL+`teO^-dwh81s;oI8!!^b8L__AT05>X4vP`heGHpu6_vz^K@*dxxu#X^(` zllQ{9&oux5AOJ~3K~&xxGSH4G>7h|&^~PNdBN8@aLp{_^yO{iGSFw594s!Ve+#^Y-@<~6X7Ii#SL^p4Pb^CKrw) zlw+EY9Rs8O?vO%Cfy(rPfTQV@H{8Z*!L&PvT1Mc;Q6Jmv7%`YRK#!`hrP}F8BT@?RT)~<3jWu> zeu3Tt-CQ?$EN6tO`M|{=A?AeGzP+BD4a3J(l9d`aR!if7Zu)v!m{1vK{uvGx$zOA- zZe*m&A(ZlA{zEE;$4wXn-``4Qd@5&; zo=8V7rj@H&{CZ92n`jwk_==)`VsfGyTf?-v3T~ZVi}Umk_|wlHWN$Krv_rU#!?G1`@zRD3^g6vvs1YGuF?e-~j3M*DkKBH*Z zwT;9mn=39E#b|3Y+cqvE7Oy0i_cN2kkdE^?QY3`H)r!+jo4}X;=`Z-$-B)q#)t7VW z>?^5pM)ULUf1lbB9gLr1kxZqj8x>6qyJ(1>Sf$Le>fNA-eALz|B*NJ9MrU%Np&CKyh$+9 z#sn*PbRzVRk%8YNfcEwPAy7iIcW(!4USG*i{{JuY`lfcKU-1`w=O6!>^({?|yLk?C z=I8k9zx*H-!_FnMXER^C?|tmuv4d*~J_6NlvM*A(sN-=D7XJQ_(Fi(c0XIRXdKR zbd0b5%QtE0+{cKy;|UE{q*R)!>NBZM3`1zIpqhSfwpMZ4sBwDdfxR^Ctj9X_bVlFx z98^!@f$vV^6CsVQTq78iKuSL;@c3X7zq=`ylNQ9a%v#nMC(G=$cXJc$`X+ zBtiz2>qc2&`nvLj@;15Q!-&`^5*>~7<;M}Pt|sSfr;j+1NChI(fs$eDP&IickCtF5 zlU65{O*9eV%P)VMn{U05yRuhdrv#4e_+suP9R?LrGPS_6!^Cneq*G~{D$gPjfq1sl z)b{bb`ng<=+TkO4@`=Z&t*&Lk6<5&R*-OJSnd5b;Zly|!jkW`W$yOP>) z9@X1Mx-SPcQK~B|3Gd%er>Z1fGmqGj5WWyvyaIb2`)*_NDSKRv&DeV^@XJw4gi zvSlSpvV0|3iLxSd04b6p2}TeE0TLhxBJ5&;4YQNFtLptxRoy+avmi(z?P7l)5<8)% zr>Cm=)H%PCbdK&tOW1nX|6<$LO`P-de@;{yL}BQxPm+@X`5>UMZ!ddti`duK#ZXis zAJ?sd;%cJ$Li7Lnum8%8H{OVG*%JFJIzKny*Pud6K~5<0)fd@U?V&KRiY~L2si|?Q zh2!WbMKoeXKImnt-a}_-=u?{^Fhq6DSiMNl+fN)8sKo{PiwKOg>2%tvtEG-kK6u~9SaJGEe0a%wU|)<1&AgQH zcDbbulFH=@NJVFViAJqTT&~e59Z#_{N7v*|s*Q+zM;E1nVQ)i0XFv7P-BeXbVbM}L z%8I@M)WQj-_D&%4o$S$_-2T;1aQ-62MQ{BbB3XjCj&N-fl#c1m_jCX5hxpiz&+`Ah z_AgjHv4}bfgNL-Xx(oziF2~f!D7D_D1U&^7O>JUgVhYiHGR0g#97iZ)oPnSz z=JQNXPxHec{*X7m@s0HM_EN9c538JM?wYI+fuJL(64XZ7xli-)FMpT!{Mnx{7&jfl_C{=(t993{VQ{glY<7a%ia$2zqio{N96q zMR;}}f8G6?sHqw%1ekkk-p`?s0fDp)@fsbADV3o|8%)mxBYXFtbSI+!cshh8zk3Uv zL;Z}@AQA)U0(9nJU~B^uwIXrviFD+q5Tb%Hb;OinI_#%1(8u6ZM2QNGVn}tm!gRd> zUB|Jkqe?Woo{6}dLjPif6o^?t<*D1tP)H`~pHVjkc3FP2XO8Ff5-Oo}p-4q5_5Hi7@y(8pvC373q8JUPjhp0nsJLUDSOYB7&QQi$S2kF>k7K{XV; zgM)nJ*8dxEULPMF`c*pj1~hWI!%7)WV+RMCiab?J6F)#(@nkFpi-+5DMP6(%EYmx+;b8_D5m0Z zWPUN78dPK;96%z7RE$X$+x+*)9ai`vyiyRw4dUD)#Gqt2o@ia711J@cy*Cc!9LdC)Wuv_8MuyQSphIdp^aQ4=2@9Qcb$6p8 zg`TQm`WMrWq&u3x5TU~mjX{|Lg?t`SkC+zyghNA=)Fib?5q7VpA4429h+~aHQVJBE zY99uLkOG5YS`8t4hv^2Csz4>D5lXf;Lu@n=$6ati%@gH$VZjE+HuXdO^TVg{Ge ztreyExQ!ik&J4d%&S%*5XIp4DWDeDJDxKYuIG&^&_K_c4L|_bB=O`c`HuQuO7z|Uv zA_4@mQ!>yki6f0_)aeyfObnIcQbZjVcS>NAD7Mw5YvE!Hg0L&3M}Sf>aXm%|>C!Wf z(INoO*`}Q4)YI2VfJD_|@aB&^ItdMJ`m z#6%5E=c2_Fb)9&sOf^_S=aOZ_4NW7CFv3SNDqElND6n^|Qm+#yo;HSZTp(XsMPEmS zH@)r>dZHTr#R6UZ1H@_yWk3#`OgGR_5kd?QYJdtVtTAc&$@L9U5(*WMLs?Sl?*$sv zr)sF6lPNk826}WEYWvvR(Z%BP-oaUyUI(H|998Ko7U=CPqLiZ1i0CTyfzXs=1#ulI zAy~4EL1~B@lT`A@(|>%9SVgEt%usg@Z6;Bs0U~fUN96qL3*zbl8Z@yH6oUeV+7#qr zS_Acf-rnU_PN+%iq+LABR2;SC^ zRFOU@h|5*t-X4lFz{J*p5fG#i)5m}bC9xZaMkAG?5#(uvgBW7+g^(qkOHr}LRBMET ztLT={XiTAuWYOSqVidJFrfy0UdQPJx4E4$$0@+1>e-4zQFiOt7*gu(MM{B*HFZv;v)d5IQ1E@~)JWxcJ|y7lmjOQ=yAemqf?=P&HWA+k;l)X|+|Yn3jX&hL+I_ zRO5&+*G2E3M8y$mdJnP?5@|soh6s$6?`cD&gQuiM!5Z z5>2c?E7wm#2qXa}u*8xuw#7$Wx8d~x5^bPSS7>R?JFQ}Cg9r$OKxpj@K!HF)q%e`K zu@xDGj+7&SBvQE0b!{+cqPnsISb&g%xZbd561I+r^&SW-tid*}6LVVtAGL6T!Pjgv zafFTy)?FY7wDW{&iD}c^ZUL_b*+ge=ObfIYXk%=8tS~m^!{ywx-zu|Z60$VR;mRKH z->)6fYwHF}B#l@R$1#D8*P&jk0}>&uXHY7QkdoL~xs?;3bws_^u)iOO>NSj$3&L-n z+LF3HEyhX=jjCB`CxrdJMy=*LLI?D$I0p$fA`*kl43p5BMm@5xyHU1}e3c>Qi=BHIC%+YcYx<=LMk^n~8T9XL7ek{ERzD|2RZh)|o zH2|u$s?{4s;A)M676FZh;;ggJ;`|HGN1KppU7@2oGLXbk1Lc)aDTsC5UM~9`MZ+-QU8J*j` z0)a+r+vE!bajaZquD~`_0-N??KxyL0I!vVu?5^uhbrgx2ISFF|q_%R~#4$KIr?KT5 z8VzetOIHoka>C-62o|u&-m?NjoUGZ%7#fW_(&^~6x=Qpv!0PZ$P6(h;uVDljA&8YC zj+HyRK*x$&q#Tz4u`y1=lnA2_&=fM6|K~W#>w;3qs?SnbUP#A8k>z&$SZhbdN-3<~ zU#%qXS=nJZqF5_b6xq72Zmb&(YePvSI*w^XHfvfsayOCPv(#Dl_1EW!m8bOG#`=pL()U<7OYvXDVk&m~NXr-~$c2)ZQE*R`! zikd8Gy}!8%(;H`Di#gbkQZ@&R``BqDpzOXcBt~eY4$v-4+Y&Ce?14KIRyDVux1^rJ z2)l>^4O)g)C~JjnhZ|UeSb)&hP%%zvb{n>@ zYGAKANac#e)d(_)T+qZel}kv3)Q%L8Xe(q~(R)f?x)dM zC0t0`oxU0u5;{2ftOSY5jBH>dG=QMV8#+7YzH*X{B=^5X*k)c*qjZpxeA3rx1*0WO zsI(1Q@{gZegVaumllGhfPeke@%0PqLLm+T7aSX@r+TX1qNCv=nfVS@kz6+ge1f>I) zf~W;jYK#$9@zx?a#z=({)ZIagEkstOjA0}+*VRDztbU^7j2aif#XBG)*$^1q>8%t9B1cQ zyE^)wXAsG*k$#`!^^?g<;wX+TzU#0dBW_6}YDo$4mZc`0re~x?Xpqvccj4;K*?TM9 z?~S`xY-2A=uceb_{Co;`2hHiVedLhpKPFkwTB<~5r8}tAh=e)1mb-WNI2TP4iQ_7{ zr1>P;o74tj0+&x=pBw*7N{l04n;M5o(UL{$&zb77kk*rPEMgOH>G?0=ztNG7iSFXp zwX{x1;|E%%_f!Hi<3Y>gIfP8UZnD;r{9T#JRUtDTcC56|BJjc}nM?agoy@Y&$|JKU zBp1;1Cw*789Me1r&Gnz>`ri8LA>9IPPP@)XjYRp$W_UH*NTkNO>ZTYjG&CMjt@;TdD7iW$3nxec4%E=G7&mX55y0ZcWSuf{6aU`^_#qz zX4++}0l#l}o1j&Kq<4V7FXrEzq&AUp4jmsgO2*DlGe51IG0jeiWboYhNXyIE$Fs?e zRA#68gZq|DH1B+j;GF?S#2K7TrG403%C985qRH|tWON+2!oB{5Kgc`XTl)h@|KF=# z)jnV=i8P)i3cD2~kAIIuO`fA}vKk!eo@6C9gVPjg)voP+j8cAam zAutA4{y@8q>;jV+a2pR9B+9-0eAC$u+x1!mEw9^%+d!vVk`3ZAC~tfSNno^-A!%yA zKRBgZWkN=oM32ixoTk4vs|MOAUjJMZBRo@?*srOS^fI&kxC!Cu$u^F$d9dc4S)JVf zTH4HxP&)$3HG45Qf*g&FW)Wx^gBxp&cF7ht3d8ug+{_-LGvDZ92<*4J<*nPP2W?)P z598hretd*Zou}#Wd6m#co+PVRYecdir}vRg%+B<6;~gaK<-kvn#>HjgNH$w0X2$i+ z({Vpo@H$QUOgKW_I3WcE@LlE{?p6Ka|jD8KUPbA64m5q>S{#+VS zlr84%Nnwd_dVO=09U7F246v{INL}BZwAts7q-@CaJDRV-mV(f8G{E9$#DR+0WV4*7 zj<+L?b^_HTZV~&~7;j{!_tKF?yVc_h#@MJq11K#?nxh&288fQ^ZS7H=_}bGmxBo`d z^vI+eY|FbEA5YZ0MP^5{l~dGOy9N#dDJ5|nx4q`Mi)=cft!-vu$1QM5h)z<=wTotP zGw3EIiTayyc8jdKFG!VZZ|{rj!N)MnEQ64Zfon33+A`ysW)t48ZqsDc`1>hQ8KqbH zo3CgA2ZsGE-}F6C-P>xhNoiO(>at|KJ58i600)z-)1b{S^HNnIoCil)c_F=Q_vi9Q zI|(e6O~C~l9jdeEYj=IEk6YHN6X=dA#*@@-S9C%`&X4sz%dgCgg@r-e=Sdz?+zHxe0Go6(}wv4iMq-2Sk4A~_l zGlqO&?`@0hu&9g%>SNnAIz7roO^S?N+e)Ck(g?Y^idW+tGc#nC*VtXex+5nOM`EJA z_+$5TJ;r4bzT&HztC%?+K893hg7ccAqop0FOr~S@H2d3&XM<9v=_}^bx^E3lZ1J`-O!KE|)VGnRNBcvgW@fg46O4(s?F)Kzf!WwrK*cG(#pF-P6I@^EF>-E1C^g zAq7%cBP8pwTfo7CaE&&=t}fv=T`6T!k97fu2-+(->2~a!5z3|;^=M+=Ta?&DmvD9Z zTwN%6AlK~DSd2``*)+{6i}vSgebt|%Rn5qrQD&|^2!f_`qIsKIG#qjU8^n=rw_c*dC+oJuE!w=%dI}LQy)Y zY%so;pnaZfZ?AA164~L&;v`=ndr0%K(h=F>SdlY6V%m>zJBLv7j+=d700=8sWT)u> z03ZNKL_t)YXYS?uTkp6o>#}W0hIzlgz7iO>77-|MVER&wYllEm0Gj?ruKK$Ai2&7oBon8(ZEz~6qw39Kj@2_@0No^5d$O`9_5={~E z^9QqyNb|aFew4oCG)*r&++Mxk&$PP+C*y-4XnJivt@(D^b-UNBt>pTVNN+Wn8OQl& z&FAppN7=K_vTmiLiiL1JZ}&gWvqkW}5-bqBhZvsRok7Sp9X0cn<}&~;g-|oQ8sWYD z0Ku$|7>Ue~yjcp`M}+u)1rp_L$pdxl7(pQI1{s+c6-5w3WYmEXMhkdVXv7V`MKq!@ zDk6%aCIP;HgAb(@#1SgCh3z9E;)aBrc0A0{IsOV|$vO>)0F1Wjvq+cLX?VG7_f8)r z)2(gxPqd?K+aIM{j%asotul|=K-NBHrEtaHvqStUo=nTwKVt!J3+ zQ_b-fDRs~c4FtxZqZkt#ViOUm2E=N{3?ECdS^Pfv&a+@IH<~XGe=o#Zp`(a6R%oMX z&>)Ut;{DYEUqB1e=v-#pC0^cTC6Fauq>PE<*vT2Yuf}nF#K^Af6+kK> zl>e0Ub-&*T460dPh#)z_K8lm)Qu~q*3gfIy@e232_VOuPl)*(8qcFr6Wr(z*rs@#I zC^O$o{#LY;#M$x5?u}NKczJ$j)_$RV+;`yQ5+KyEfDMbMY|G#UZ(p$!j){W5;JHsS&fFZp(17Fdlb#r zaX&C!e1AyylGsWb9blVd9{4#mprbf>qLrp$8sr)fW1J;&49FalN)kkSshtGaVGz9at=P-LjvEQl<&DqpF zI%B{{ON`oped|?J8VoU^@xH~GiKMODQD7hzDRHp>Uz-}HtsAYs%Kedqt>^aSt}%uv zijr~-^BG>ZYwpL{N`>za>9N91nn_-1rWIz3GSC=Q6k&`;NkgO}M5L3Z1IHq4fUO7Jr>O6tbO>NfMu_&>PV-{>|UHJkL&q~jh%R&Q6> z02w8%PQQTJ&^oHk^WmdHtyH%pO}o2E2!Yl*A$PqkH9zr6bjJ2*F2SRnm;05SPpKG` z#u!BuN2o@OR+1>vZWox}l0Ml%1&IKjP0~vPMi`8mZHxu&N{F=^!K{oo#v}n@^QlBb zYl*1V)p=lA`OLjoi;RAhDYMXOPeBrJZO>0u(cMw;qS*aX8*E2 zjZ!oy0wt}|A)4nPjdPg^tzA<)pE8Ye4N3#DwR`ORhu>%IUxN`E*|cwG=jUhm9&JKJw;8SXQZupU#Y$G*96b@dGhZk3e)e;%uQj1X0Gl?)YA3l} zOGg_qdQjG$PmQ!jy{B-cOVgFY{UdF2$+^DX_fHA7o8j3FZ<7$>)=`^u|IX;~*{qlu z*@Om!u#Q?=DnO@tWHuCJp6{)%1gY8MM>lK7wU_lt$s}gZrq7`i!lg|oS-$>0SQz^Q zM|13+K_{80(#ZX0cpWWecn&SHfG^^KKi6DC zI&zAn^pTO6S@<1;mD36ZqILK4@uR*$STov5q^yhw+?dZVzMzR{Wu|^~U|RXiz1Uzf zO216b9k?E)-k!2qH5r=~T=heN41&adI+R!-cn>3Ntz23fr1{rexDnRyg)B;`}cCfV!61_zw`jM9_S!F(;eKNq29dEya z+9hI&Szd2139K9soRX)=hSpxtOJ5y?eFIIh}a0@5+Q@@qMIZ zwjoc`Y-Vf&AEeYKBYOVttp|?g`LoabDs899WbT|~t!gwu%Pa$JBk!8eHuu*1p+}Zj z&%(}#)qSN4kF@ee1A!!?A0JC=+UX=nq&7`F#LMX-&1bP|G04cI+4dNB_Lj%99oOca zM25}fIda$U#yHR*o!*Gs8+}l&0Ps!jlg9x<3otQ=z_Nx~xgilwqPXwTmR(Fsxup}E z+$|Z!#%Dk#S2|Dbhju$=JN>A6WwnzhPTNX!e%-Y0{Xi!ck#UNTznPi>fOSUTXY(cx z9To`Q!-!Vm)T4=rm94^7Cs}i|5~!KawEezUe4#Txn`G`~bk5fATfotW`C5!dSMvr#fLaV(T(fEK4)lU#32KFXS+?PkK( zXw<^0hsJF@28|JE6o5V2$A_h#T|f#YWHN?AyAhYE<452WavR{BrbE-R@3$ZsacPP6 zrWtP13HKk}w*ZeU!PTnP%eJr1oR}lK_FK&l|2UgO)sF6H51APXD|GEcX{lD7Mv5n7 zAgSE(+luFOv-+pZik#+DG7e;|9(yZVw>*~kOqce@xrx)JWx_==m(9W@CAiwbkSY1I zAl?r$(!18)pVH-F+K9XK-dz~@R}R_o7n<%Ri6g>V!VUa=lS;B<0O>mP$IRd-MUTkV6+HQT?g(%&9;zky&B_>OP31)mgooknS zueE#ID@KQQycY=G!-y;qn@y`r$y6V$cI>IR3s}Gc4hHPTl}0ECSGqu;RmAo=lGBvz&Cu=7rgl~&X$Yy-aMB2hP`QpreFz17`ULnM+(L#RB%}A;xb=m{<2ctwlLbOEm(v*}66A89g# zE;PGJJqbDg(d=gM;T9Tcf`n{rb{}O4VVZQ3ln84@yP`g`bm}n4&YKUC)W*1eB^rH*`%*0syhi{6lA1tijoVZW_rVpo zR|MF!+Jpjb#>~&8Z@g+5+45O?4H*won{zKHD+^e_k%3M;laLyS3eW@rU<~&@Kh0Nu zx{oK#iAWT~y@n;Fkcp`&M(Y6^_Z9ifUEAomZW)&^8=$kgk9Mt9k}6Sc{7~66-d58U zsWIABigmI?XWjJ4{%TKZQ>ayP-ryA~h;td0+0b(C<3jJBEZj#k*-FUZYIaH^oCS= z{~WW7`ay@x{B<$u(txpzE{qgzT?dXdQyJ^>=rM_Ai!W*Sj&tK=UP}dp=T*J(Z=@pw zTiiqvZR`GJn~L~v^PkCFOyC?C($#KH)4u#+nGYl$NcYss80J6OYh!oP$baxgx+hz+ zMxTFQqqRtg`0V@Ep3up*5NMg`>z?rS(!QypU}A`R$Cac_hsXsaNaaW9$2Il&wJ^4_ z>5q-nv)NF>tpM$q87X1Dw+%d0*x%lqO;2x{7w_aghJvV45-{_k69_&oLL_k&w%)%b zC!{N{<4LI<-nc`^f;EG4u~-4RU=Q9v6@_DYc26&#et z1!H<=lHZq=E6E^i{rluJI*F1`?he^(ec68wt)jC)@E&o@=F3f+r!;#O+9~-y+&g=& zgR^kjUERU`h6OBO0S6PtXx9wL)x~AQ@N=gGtm=;0Ii|T~ z{U~e4LSC5e;%gfXCk+-^Z4{Ugv_xr*K@m!cif!lBKnMaEBDBUB5(Ct3jw!`}aV3eh zZ9ZrBQj^vf)z0ipba9)*u430zvb7%YF(7!n(PGp6TiOL^%MePPs@e8<(=s+zWB25V zX;Q?6t^KGo6C(|avn@3ncGBK~j$%iAJGg<$7dTWnU=`fh#^O19$wy=G%YDQ&;d4QREb4qd~0XbeGMYr*>G($!Pfk+S5L+gDIJ)3B z`7jLW=qOOB)KDt2)mF7;Y;=T)fnlQFUXITdc+XWOF32@d^$AWK7-Z?<0e1apGt*O& z%{%kFG}7Rrl_mB}$Ar0%!MFb8k%TfJ$OSYSF&INfp-33!s8pw&VI`6*Je6e588FhNRoe(_zx(@F{OBKD zbmLq?+BigOTdrnsa0nqK`z9xlLBL>FAJgSBwQ7|#Mxxo-8X}MD8$mQ~gKvQqjK&T|*6O4^Gw?`(%%ZzfZ1~8IXx|Bnm8ZL1n zqwJMf-oQEpjWl)}Fq$x|3`{M4>5oULxLcOiVUS< zNLQ&yxjaQ>x(-MRg#yMXv=Q|84xx3-)Wjr8t3)m>jQ^`a)?F>o*-QhIHnQ_8-w0z# z7>wPsTSHIhHC)u9kSJvcf*kpLNL;Uz%jX#E8KhdTQ>#?y?C7P~(Lt?NrCcsE(9=a& zEUOwUdOqQpRg&c!DJyfezD&+}+Fvrm1C8$P3tzM_p z-9y}HFflof3rEx zIPg+^$z7$@MlGbL`I!RU6SREx7hV%_~5y~Qs=`!U?#okH=f-ocubHs7o z5xTaKIYLk~=*+*PJyIsesWUY!?mJrSo#D@m$ z;*1xtfCU^Gq-7cGT^WWUS}WE+y@}PQoJ=kkQm;3_XpB~v?gG8NeVm+=^oy8Wb(BbH zbX4QGLXj(0b+hG>y^KtUAbNOV#}>MWPePZcS^K@a7+gBc8RwkEBac18jvYHVam`sA zzv^^`beU~C$0&C!W>Ky|5Z5qL5@}5!pfWYZNUhG`;uRF5X##CrESChOQ78gwt3@eg z$mjFKMsvp<_tIJFX3d(@2y-zeYLE#5ID#X8_+T`cK%lk3Xgf|IU34?Zz#x=PB0~We z^(@dvVNU3{>9y38802O15Kn6h?FK}n{B&BIQ{Gkh^8w@WeD=l&_W^w zC}krZElFo5P-#He<}gABNklAw6O#S z1ZWX4Ha@~c41>eN$nvy}`3amx8EYp`tVbg9yOL+49F-Cnyc%4bxOoAn}t%(_HoAMb5L6|EM zONnkw(NoGXJst7fNK9AnGD_@2#t|Y2kc3DXv1|7;1cgPEx_StrhV!A?NV`~Tn{^Xd zTS3Pzict!1jd_wRjs>)Z+j^U#t%D$kZd8aSx6?7SnC-j9*!c8jR-SVa%a1>v0lk-J zcT_RCeva!L=kDA76O-%V?29f&MGa(JLl7PV(Ts-zEf#6gxyo3b*VxyL&)Bdc4;i?v z+#D+vc&*$4i7Z^-6YZxR1v-Ze{r7(>V96 z(|G*Jbv(a)H+idd$x59ouf3eH(XHINb`$->Cvftar}NTFyV!isy_~RW z6^na2c=)~_GgUWqFFKADr<_4gK8K7V%#0EZ0!v4EE<-ZcWxs}`>=sF0i=Brc2$>q) z&QETClEur8B@Sppa7pVL+uGbIaWiFgiBD z*{fG`;__wGYtzVFh^g(SF|h~Pe-c9GFf>pqAeW078F>!!L-h0xVj2xgv>t(?(hCId zVMJOB(Iu;8`P`&aJhT&R%{A40R92IJl0$8X%x|%b$J>3Ire)cK8cJo@^{ky-za81(Ds1k&>woylCCsGD9qB`ZtGP+(R zkb;LFeVVU-`!=Krc>7x;uDI$P=P9Lq1k`+lBRC`h-CaeBg-+^G1F1B*Vh6ESG~z17 zd=V*g7_AVlnO?0@rC2B;^C7ib9TgcmI!nkPz~~y)u}#$XRT$YTxoLfcx4vfuD@7-b z@?MI?PQoChTB}i>o(63amD@H4@O#iPl*b{R*&#zZ`b*^VA@ypVKnCQ49E1T+tlPvx z-`&cu|M{CBUtnzSX146!!pYrN(Oc>yuN9SAm9Fk0VUVNIP~`I=V5rB2IBpO(8g%CJ z)K!Bh4MglH8}uUCj;h0=;bSO5`_>&zp7O0`N}26Pni7$caj zRzMiKI(u3F^xfRNaXY{L!9QlGtDg=G;0CM>v!U#klZQV<)2!fDYF;5r>Viobi&N52&vT~W=g^fR@xA$cJ z?Uwai`qq`aZN(B~RH0I<6V)M<`?%w_&$9Bu_wc3_ucua>BrhdFn5P=W2nmH;o?1O7 zj_P!Fc3^<1$ucTx*vO_MJwS5+Y4ci}?%vna&0XL8Haj2q0axGn0lxL4yLoEEF8W`L z_@#Hfo#~tZfq%Jeig$nbZ><8YU zcJ(m{$>;Kj(AJQZGC--AKnik&9ATKJR;@8rnIbJ`b8Jw&KJhIK1{4DoBGs@+^_(5LktMlp4{v*38MaGBEWqBZ2 z_r)(W9Y=JiJ?z{2B;UXHSxU>!<<0$zdE~D9S$Es*tXjQ-H($1fEsx#By5~n3k&F55 z|Lc>i?u_Y(Y-IaPv@#3_KO^~YBFp!9_-1Iaj#ziktvqz^T8fK@DHdVv<4k~6z_Q^IalJ-UNb5X=4?>}(q#D?4(qf^+*q)tK zN+kvtEoLxoAagmYwHkrYZEQr8F&Lvb zQ0axSV<};9DEEGBmFF?X#BhH~;-_*)clCJAUgU zJUp6br&vU(tDDuUh6!ZIWK?6b8sc|9{WTt4`)M9{`X~I-d;f?_UvnvUJoYR>G|iF~ z$FsDYM;8LZyr92-038S>Ha^VXfBp+Rv8T$)*WbWHPi$v-Po9<0C><|8N`GI#cklfv zcRjg>^RIj#Z+-h4Shh^lnBK!nyPxC6|Ndtbwmic0!`~*FUW&~3W8wzKLLpeoVcU`m zT}8>JwO{8;|8Wb~zVo+OoZrj0zxOScopvTyUv~kw+$9VXjwfxy{-oWE`{5wCo^FA*9)xTmo?%`v<_qV)j zRe@i8*BkiMH@?EQ7e~16ZNI>|uel64UCA^P^BSUSK5z`c&xNn4`1Nmpi<=*KfOoy~ z2KMZDi6`&5lk?yH0ZuyQLMBIU;}d`JKZ%AayzcF9AY3}k)`>Cx;}ictPsbvD|4%;5 zS3mtV#&&LJ<+azc_SWywkyk8#&2@;*UM@N7RQCShX3kguq5Nn703ZNKL_t(@GQn{} zX}Z}wU+;$!uDA#SO%P1-gPXs?_MLlKe%k9OG>Y^u9po2(_t*IR*KTKeYMS1jeoPb{ zj-;pqMspKQO)j5pJea|7q%G8Q0(Ne=soqBtQJxr`R+(#>sDb8|z25@S(r>Z}f>0AN|AMqcrv~ z_uRdf`<{=v@z4GTMhL$8u|K7!SY^$rE4k)P@1a!e;?XCb;EUh+4yT;Z$Jo{lJhZ#W zZ@leo$fqAftht2cXP(PxqBm{fkd9ThHazMJao@mMqb#$`#$pf zeC#hj$Q^YHEei}$|k^<4eN-{CL+^(R#OmU8)r{)W{{ z#`xb~`diMp;9@@bq3hZB+&Ir}+(^grllaqrd>0e9{J#{U5pMkGr}&j8?%|0Y6RcQN zAfB3Zsqwh7RQtc)xy&|X`JQB@X>j$uIRDx=aQz29z`AdIofkGeM{j2zpZM5c^6z(T zVDq+JJocS+i0K?TGtTnW*YMH5{sK3B`y1$LowL`R#ydX!_x#|;Yk79Vdd`uCfBM1~ zxpUnv?q0tg`iA&#U-&zo|Hj8DZ~QK~Iw}1E; z_r4VK2Y>XJu;G60dH69t@EaeXSb3iR`7ht(jqmskp1k>=d1C7r@BE{GnbsPBZ7e2|Emt4&|&hO#r@jd+UAAW?P)oHF*82?L^(7tB~7Hyv|K=yMP7ES7^Z2b89p-Jw1JV z^rL^uSHAjXKKHrL(79wK&z4IJ7E6ri2HRyH_q?!+;HAeXop>HsUAKk@c1hOm$g!6q zEBkU>fAMR%=ZimJZ0|lM!eO=u6>bbAKlJXmt4T=i_fPr{vwY*_9Rd5 z4e6uE^IP|_cgrZ}o_!8RhM?k1FXX`1{3WH==l6YZkl+B0KykkYNk~DYCVBSx$NA&G z{0#s0Pj?WVavp=jr}I0%^I=}|hPTo`yp)Po1mfr-S+dz0N=0;blz4pIdh%i9w*RD1t#IB2*K_kvH&EEx4Xs7dm+AjDzFM>~QD0_vk%**g|7I>h&o)!Vukv=xqcn)}5Sn z>04Q~X)Ur^rCwEh?>paRTgREKuPo)~1PF2t&giaPY<=osF1Yk!!dw9z#~drxY+8d}aombCxaP`hsXw)r5EvUD zFBCZ zEEW0OXFtn{r@of{b1&n?NyFZtz%sd;$#TTry%XH}!~1#No8LrdeJ@i=*&x!SWAA>i zy5;ZOgaDLq_qUX`3_zG;aLIAp{@ouiv162XzW;sPyZ$Nu=np^4Dd%0sX%}3_Q^6X3 z`$NCUm%jRcGPv?&zW$BBqbFD7egEZ3dioc!_P%@A{^AxcIC~Ad?!BMg|NadgDTiz{ zD=7>QGm?v0R@g(Kz87P3dZQd;@X_m983ubgx%`4NSbW)P!eSSjo_&dxC!EL~x81_o zi^^Ph_E}tf(Z!q;Jje1aW1M*MX?*C9KgK_O9{%(Xe~YmP9%R{)6>Q)BA`giQ+ji~b zo$vZho_=Y7#~<0r$fhZ-e)}ct8ZXm`QVk}Xb3YS@Zr7dLcg`F}bIXHv(;#OYXP^6} zGTWj!_)|Lb8WymC1srMk=v^so{Y6_gQc#|nLy4NETPM-#)`^el`<1^Q);H=bCaKWtF0Rq5;Np}XE-Sp-W>#Be^( zJFd8bzy2S8#n^N=w?74w4MVw72cR+f0(ac>6&}2Q6I=JK;)IKSjn}>T4fGC#7zD-6 zKDv9CqJsj3u5PZl>Ke|w=n|H81+e2;?*01L`ROCiQ|USrAqS{b7Xz!w=etqb*Rh)K z$mDV;MWK6$RagEZ_1@#yylo?oKJWz7`|8wc5lR$Twt6+Mz3v)Toq83QTyPy{tthkc z;qUO$rYERPPco?^%AK8@b?sZY`l^dKF}In;iyNGA?(v-3U+2^{r*hu4S98|Mirt&; zVCUxbRHw$7jAP1qfoePC*;Y&QJEGdoh9GK03=a?UmP@Z-`S?EeJ@YV6KlT&Kt7|Da%jic1rpEW5kJVtC@e;4!eTdPtU8mcE`$qbyr@_ zdAWef&M6uL=X2b7SI{whDyOcwnl-D2+4AH)yil2>Ysn(U%hRlTZh|u1bPRS=T6sJt ztX|5>0SMyq|Igl=N7->)XP&=^%zR62DC{5>0t5&W1owRrcTps@P_krev8F9q?zSzv zE!)S(WBa)K%xT;0v0FZ;XL@Y+v`3cPR=3=?x;-ArQp>W|c1si`k>Vn8r?>zFKx~CV zEpN%pxG{f3L}tEMuL=MKkU*h68~|1M^5qh7X-15KOy@yeu|-FXEPp?%3Jn zv0V@FSp8ZihITS@U?1xyp5e%2-#~IrtGkko=ikh_?KiUOqDxq{Z8eu}AEss|dG6&s zJb9>t42>`}99Hf?ZK%nOZ+RPc+;$z?+AlI5jd1HNZ)RxrWgdO#AL$s2v}Sp3*A!15 zwlK7ekTQ2Zmz0 z-5I*GZA?vb!CODT9!B}{S3bw~t>fqztTkw>(00+4oId3U)T|YaMbpg`Th6n^T@Y9$vKzb#gO)-9}Dv4%hT zgMY`xmv80sk3PlyFHZ9KGf#8(UAJ(}_1CfK&G+%Z%e(l&bC0ld(->>UM#-{->Di4m zhqpV9=`1666>i^(kVuIYq&9|Kb2;Z+aWNB7&Vw(fOwnX?(|R`F`5yA|RXqRl6RbFQ zgi9}7$;ROpci#6-{?C8^QEt0v15ZBrEW2lisEv(KA8K&nMHg|)-EU>v##M}UCfKOcSSpsVge5GYge%ctiwLD7feb{@?zBm2 zBYgN5eu2F+{}+#a`EU5AKXE%x?EV&qOpA2+g-qIEzWl^Ak&00k)C) znvteMj3#w94)5ppe*MF2JSXPu@BUH#{9_;GiSIwd%I$C9vUMO?NI$xWe;QhB=%oU?4U-{Cf`0Gb|Gr*lD+T;^7B) z>{|~~OU@FF+wc23moP|s3Y zlLwhK5v|%dfBMNUu<4Q}@%T=B%lDa!9iL7#n+qeA4VSX`9y% zf{r34CXVpnL*M0@C!gi~6-}Of>KXFGQ$#DK7_vvW|H;4Sv5$NPSqVF?zKLh{@8`

e&p}Z&Ez-&A(yIZ5LBl2Jrz6LzyoP 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

t=gNp@yebQgamF~&OIxJQ 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
VLTEVP!v{ZEf7z()g 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ƥ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 BMPL8` literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/fabric-import-notebook.png b/docs/.gitbook/assets/fabric-import-notebook.png new file mode 100644 index 0000000000000000000000000000000000000000..bfc23f9533b9ab28f823156884a04509f751ac93 GIT binary patch literal 39438 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
wTky%-?-7mw>T?r2u|atXp>3bOni%YJzLRB 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 literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/fabric-labeling-widget.png b/docs/.gitbook/assets/fabric-labeling-widget.png new file mode 100644 index 0000000000000000000000000000000000000000..87eb43625dd464418533f379ad28a6354e1c8da1 GIT binary patch literal 37310 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 literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/fabric-lakehouse-upload-files.png b/docs/.gitbook/assets/fabric-lakehouse-upload-files.png new file mode 100644 index 0000000000000000000000000000000000000000..49cf52871ad63820321025e26729641151f9642f GIT binary patch literal 64740 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 z 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≈(@va<4`Ukff*Ql@k4v9!Lrm-PK9Ia}k; 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 aFDQEn#7M7S$Y{WTKPfSJ(F$Sx!2bn!@Z*pG literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/fabric-match-output.png b/docs/.gitbook/assets/fabric-match-output.png new file mode 100644 index 0000000000000000000000000000000000000000..c505bb20454bc2ed2291e93b19e7625044498bfa GIT binary patch literal 123965 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 L7~pD7+eiKfP%9^U literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/fabric-new-environment.png b/docs/.gitbook/assets/fabric-new-environment.png new file mode 100644 index 0000000000000000000000000000000000000000..1d13dcd8b1495ccea56a3c37875d11a36e0f6719 GIT binary patch literal 51531 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

1ie1o7jZAOy zwLw-6vD*O8G%L*B5;3Fc64bpuaqO|Rim#5I<_dY^%yTm1%TSzTq}RKK_AB9vxZ}RU zq$q|$+A33Pke&o9PjC<}R(Fd^3R=G+UkGvS0!CGt>7yC^UP{LEH@eA#CBOgEr2gZ{ znn*zewl+vuS9QNB!Q#S5w|c0!;c)8T)QE?{_kKncZAfq2G^AUbvkz@dldz7<3Q>>0 zbV>#-a!a{e5dUhI5!h8sip7%dN^J%2l*G3W+8ofs%CEh^p^Y{7HK-jDhZ~f2NJo-w zeCJqC5^*;xEiZI_u}4`gXF@3zU({6lrlGG zisoNYW8=Tw5I}H7|4Z`!2iM*}g~%b?!C zo`T+=lUD6`u2;BJV6LbYtEDuiIV{)|w%OLrZBM=*iS9gfIIPf4jX3clhpDR}W#gU^5rAHtoxo3H{qyPm z<4)kd1|JO);AvW4atEWreX24XtPc)GXBIpGZ2q4gNpotm_OgOGSG?Ga0s>?HVX=*?R3RAh~79Gds+8iHm_q`Qu$Exl#y5h zN9lYlX)q9i8ojp4gVKKp4N@ES+Yq}V4Ivh$-4I}WHW1O^L}tzfs3xbsrhuqoZf`BL^t1HER$+!{EOC zGuf`q{Dat{WKb1PW2@-j;W>l(IGej0_bP6%fH)h2DIzt+M5~m}*ZOpt`u)9=5cJHc zmHt&Q1t->WC?fA{Ovr{5T(LzNsbS`x&HfzMSD0Dx?{rTPBnCG6EdleDk=Wj5kC?Pm zbSVRykaPn0k@;9awjr+quuV5mK^{kr$_>xn-+=i(CM+>Lb6|bDaD{%@qm3Ka+Spr) zhV`yCoL?yIKEZvA1%LLkc8V)F;CO-&Z{evf=l0BM@;OtCs*@O-P z5+7=^#fz*7G-*^&V@RLmVHWW$iHAdD%c#9aF=RV^d#eP;0@FYOTX4ibWQ!!&M7V%h zLmRegJ`KFf3dAM=AS3XiFPIW zGO@FqcUS=wNUhIrLoO+@Sb7jHV%tsY+#<|XVqq^*uKam(VNhpQU~hi^=&dex-u%Aa zWgxIEeG~AvH{_-n^@OaftmPh^IzgbcF9<*k3=DX|Z$-XejIDfP`kY=~(t}>$&TZxt z`*v9pFc904%5xdGTMKPTAin@WR0?MyeYmhW73=u_ib9jp)@JTuEMc(quTEtHu}fRq zE6Z*AmA@FgGlI|b^fcS|iwH|*7GOK%icAeai^!i&n?eOKh6ECWo1Bs11@PrkM1Qnl zk*#3^ctts8)Nu=xb*Gc!Ql$|7)fo~Wi)z)1(5;U3y%kGWWLqs*5J-I9p4-}&cWF%d`zANSlm>ksZ^ zm71B$t0sFgMIqZwLMgju0$X%s1dvC%3yv*6X%u+V>jvx>LfcKQ zhMftZrr5@U0?{;a-JQQ+n#&;YkLMsnKkVOkhXw)c0eZ^%3W?Cx1|g1J`maj613f{< zV|YLL-*L|aFGnR703Npn|N6Jh6W~c_%l>qrbKWBRJHXhkzGGl7SCM}^jzkQ_2FjAg zCz+{Gfd%aN@w9xcZKG4%;!=q!-o}rkxNW;U?hz5>u^pTBf7?(O`0p;M;_%LW-3ji4 zO?&2i&ZWj3Qz)O;uNXo*h(J$6KydDF&6=8=k|MAocr;=#u%Kz?khfX+<}5xZk*Xmv zZ;;2t$3|@*(A6Fw)7mWwd~|-f9cKP5KdB$~S9#?p`MCU~e(uDB8t1a)n6ATHl7Lh; zi!=G)UYXp;0<@jrHxxeS)@xIe{PO!Gh@Is?)@D{H>T{4IH*r5Awh`7 z@Z}%Nk$7Z7mCz*%;q$RglQ*(qXDCQWLK>Rz##PVvl?F&3cR>3uheIeIcZasVW#2CF z$lr!}q=)wU`XI-T)WXNWs;QV?03shF4tb~fy{60?F}akW7vR<|A>c@$A7o$66s$~5 zhny!ggmo+HSyVo>UOa(G@o~n3gCTr+YJm1c*NestT#2x zE!H<2jC4vm^={iv&Cc$&@)8pA19gu%+`lVbIGr$s2x1KmL_u9fR5XabUN-c%CZ=mT z<=(q8Ajx}l8kpSlE@Uk=c^U=e(JB1Pb+T{QIWd8B#r^&Lv!$@EZkBA)gj9_dy@ESW zki)?A*~tkJGc(HY!4<~aq^_t`Fxhr_d4EN{a^`oercYdbkj&Z&X_^Up8a|d7PY)MyVT|!#4;pH*SNQA+&JACN9 z8hd+m6-ljn?TMHGAyE)N9EdkpE6iS2HB7Oukx*jA^bHBT%=HYc9X*0?d7D5*L_{`F zniN2t53Y;?U348+`j6tE%hg4S_#>4`+ig`=(-#($>(ptBi;LOzFaO$&$kJ6h+iNL@ z!bmW$lk?Kcr)m<`^Pd91iNqjWkDJZaO~=@7y^5_#uHMK?Bc@C$jHcsD(=en zoi2ltD&rH?dqpRRmLXg>cjM3y>|lM=Q?fP$dLhRJ-s8vM9YOkdx)gP9uw)piD$BsL ziq2lYJ^FWS)*4rND3SEN?l3`wr!C|^3ZVHYeByE1)V5Z1^z(wOaCON=nAM0yfio^V zq>P*b?{us%(|ecmI`TFX8 z9hb5uuGyKY7;r;Q(f8Om=kTn6v>f=u3wWJA90j{?JvQtX3Vpdw&Fy>aqkG*;F+*mf z$9mIo#O1kD>UoNELG=anzQ}igEH%$GY>bQ*tG(VKk%fPZ@!f?VQGl9l^9h($xJUD0 z=jd$*!Zp%sey?10YVhNdDg8aVT6SF0#1)7x%y zGh@;q5wXtTT^8P}^C2mrZt9G?cXhQqPn(6`04BdnR4K$Wkfs8f>xoT33+Bhnt*jnJ z?10XIX?5R|2Pyl*#m(K}HUi^vm4S~5^%v#ujxgo<{Xu$*V&!qy-ceFb*~XJ2@5kS$ zjnDZgMxfGm=WySXwlVA)AE$-fEu4w`y;!ll-S0HWuE?OCO;1tNFl zTL0mgyX&8v4qCCJv%60E#_<$-Q~wkdkwEu>lbKqAb*=()Ksk=hg^8Xk#oP(xGSY`{XPWU_2q=((?iDI60K^9!7E_@GWs z!J*o)qY}IdQc_TLLD_hC{sPbV#koeY-5%DFgwby}Jk9i&+!cT8icqFmp7DiI?W*?) zwwK#|V&q>y|QjFx_&?c$O zurdb1A%&DVgP|hBRy5Gh%1XLikiDIl*ZWB|Nsod^jpMV;^a_p;o3oL~1C-J-*;1R# zcw+WgF)J+1uXBgaQx5PF2Ll423j&%LP?q& z$N~*0?y9NRe8#I4$mxl2Pa*1Za>&>W#VsZlja59>E9!}AIENjv&V!O5^u7COpggFI z!!v284pIqqAg?ZFUdb+zNE30HK!~W4=(M~tPj5}kMFwo2n61Cc3U>$ihJZyD3U?e) zd+gP&AWQ7#@cC{7yQRKkyB~fuR&Z`Sdwb+MmH@V~n4EyVm9T*F<(AnciEb3BuE(8} zft<8K1PSV57!H>R=llhI62%m>^bJ!f!12pB8$qOSKpr`!xuV zRk^|09ZV~=WuEPnkXXD5zb%)b6)#sC*JN&~EEzdv)PDWaNzt*Otg<$GsK2B?xSCD06tjDeF3w@@?&u=@wq_L|sy(@C_*%#Qg|?>$G< zBU`_z9wR9lL&&D;rWADMn&r`mZfn8|q+XtG#Ki zFJ6=F-3rt-QiuILM>S|PT#>GYJe}nQl|CmFS|&KhEo>LO`MQj#jnLBU%hm~4nqxgB zDPvC1Rv#}bfQJ6s9t6Lhp29>B`M&09@{BS{fxsaUcOB%lz|1%UkcY`m7nZffG5>|T zK_EN7xQ0hkgmni@MgK!MQh)HZzx>e{lq+ zKqX{cN}jQLbLfzfsGNcF(19?AXyCoNu1=m1SUYCqbG=py+wZ+G(zJ1Nn85_aARHs| z@mT8n|j_o^&Wuw1U+SGM}%PKVEBw z=-HWm44#AsMwuGV%aPpOn=?S|R({dme{)2BOgzm~{ z8M?0=F}?&PR%2#!2dux-VCIeK=q&FB$Jyz^4+&~7Cm%Fpk)rgBY>`#-f`~`iR$BEb zqOrJ*+j3%+k*9NxZB_{hu{pjm&C(~&SjY15xWS)=Vz1)|@8xZurCD2_?0oJek&QQ= zX_b;RZkF4}XJ{#NRg(l{6Ec>6sZ#FL?9b9sTezR`Cy`I?8gp|3i%TPdeZAsPz7VE5 z4{(pwVe3AoS2S+$hMU=PWSq9RxBS;Md2~ndN0= zeHU@{r>`Cs6&|<0h1>IlQnd7kqlI(4X8ykZ`xX)`@W((qR^aB0dHZaRPwN2s`%1aM zxXv#P0R`E3xKM>sye6$*`~WxSbAie!sx7{g4ns_pdXer0rT1^a-T>ZqKYPo=^#+wW z^l-cp_`j8Psl~8|Ha8oHiC>Yg8#`+BY3icJDX69=AedYz$TD217$~F}YF!gpcvg9% zX>!9=jT{e6&{>r`$H9OL`lDpU40FA__7D?S#s%7rAPpqOyQM0l@dj&VP8MOY@w%2~ z#=L2h^Vd{HOCwC{TCdzt-8uW2K|VcMb4JV?mz=hT+A68cQQI24@YZvhZS7@oatfHl zFp)6Iqb?3(v(A=At5;G?Y!7KlJQvTnp^3Ap9FCYQ_U=RPJ6$rEo-Z_1eF#DmU3l$y zqlt5<#*($^NzbZFGkf~edjx3EIG~=IkU)I$?`UpukEo3H;rc9HY$=l_)MD+C?QTz^ zd<4O3FD(cg_J1=et*J^|>#p8i4em=i z0qu!tyzS^R0NCZ49hp$MVA9k6tA%(L6U+s%OmoerKTxPQp+VY^3(l+S8K-w8^m|zk z>=#YSn=qL#c5AG#>qo*EgR7mK_T@}zZr88USn2!+lfHYe&6FmaZY++nll*JlF{Zd$ z{LYKgH9dO76FgFGzJGuDI6|8g{t*%UZ7N4iI-@mMk$kbXg&|n|vvyKW`C7dsmm_`t z(X5G#0Q&GkcX%GDw;J5Z+1XprPK6VS3(MQ9ZE*bgm(APdmpEdXJi9nMa+q7SIWhsG zVUlz$y`yMx`l31u%=^W^izj3h6bVoOvL<*bZU65NuH5}&UA|bW8KRH#d&Zy5o#>0A zh71%eTcOVXQwwmqbqi!^z0hRr{br1vTApis|83K`2XVNEYXplnlPP_?tE%H^ zjj;7Q)nOwVEkWP1XcB`T%eYUeGtBguZh+z0x9ubYgZQ#7mkU=T%6C6|({b47UU8q{ z_j|eb)9B`x5#Gt>H@#YtXo6T}hw;zeK1*PeXzF`M3Zo>a4_$P%aR z*mi>M_?7P`S{!#ZyOrirgsq;4lN>EUtv8KLw<~2vC(0t*Rf?tyJ~m_lJ2sn5bb_{q zxQKP@&&8Y5!e;Zh%MWzr0TnhuYrdNTZeg3u%r_zkT?Z-y3S1;H!3h71b#H`Vm7-}p zS=NPGrayqV$a_QE8n(_lU{F>chELj*l_iF+?N#O%){cG?m*^H%=MZ-@zrN zgSCfksH!tblf|S3j9k-!rIv8l@8*Q-eX7^Jrv>|7ccxzVnLZJ@uZ&2s;({lu{k_Q> zL-*z6kGo|?*sc$JI-r*5n=7rcb_htXnv6qMXa$^({woMlp{aB!_slp@i+pVvZ8k}Y z)^s_naLn88oYvCZXa^B=D96^qB4X$QDnM)N;Gn+xm)D}1Y2O(UHZ(swc>WL2-*YI$ zY<`lsghZ;7(?F75$&-kq+#uqhC1D>_#4sfADB3 zb@qpouZ(ou(A08LL@zRdy&#%=YYS5GPs|HslO_|FXUJF{tB>0TEn0@?Ebc#FJ8WF4 zKEp!nSQku`;j1*pDj#DBUMQj_nf7?v za^SYt0;+epdmPu?u%W)k5+_xrUi)dZxp~l3MjgH$-+ddl7*jK8DM~E&9J`b%51S&7 z8F_aE>8*&@4ol4kJE_->h@BtLqQ+GhI=pGLGK`R?yZks3LlP7uQ-k{0g8lH@_W2M3 zHx5`E?79=3n{D2(d%)XKLM;gF+eo|C1cg$x2|ODP7PWb4{LN8JG+{4e;Wzj94Z?SQ zl#pJ3&+-&Q)Hr_TwtoU{^xO9Ze4GS0-j;DL%WMlA`$WILV7lt1&N`%(Bj~?+{;=9?NqL&`b^m=|YjXPS9j^A!Np{>HDtDhbdO^{-r}lCA zVdc@l2#w#$b3pntkNc z@4NF!oAH@<3di>)?D2-TZ|g)g&O%XilaouQCpi-D{P5oN*wL~5sBtZC5X7om%6<2q zFaLX3@C)D+NDtHa7XE#dPZO^FS-In&5Dp;%fZg`JR>n|5~HksgRabF=yak?eKE z+UYeT?l>;~__U2}EL#&a7vV@p<@oik+E~Mx^5FByHx;oy#P?N~kv8cw6m9q?{d=-k zp6}kp$NLkxY4ZB`K9rZVZH?Fw3dXYGPUH^6JVSDU zyn3635kcnMsnh?JoSbaN!s)e*4`x|qr7<@V73oMXVoA8Xkw4|1o3JUA4!T0ai#}e$ zZQ;O38lNU1Kupyruin_uAnP9RzC!XVX@(_%Rt~(e{ zhc`w2M2FYy8jt#n>TqDgHgBrkKUj_?kL1a!rtQ^>av$3Yo}ujqz+|vZqU|nAGIu5A z_J~I}pXP(1mh7Sa{7h(NoN9m7b*5Z|8Rw4WcEJX?7X+w(0Zi5M|GJ>{X4-xT2E3=< zM2|MViX8W9#Zq?$eS8RtM%56LG^MV5zc61|qe_aQ#Fqi)6x61DOc@tyI{&funJu~(G;{#wW zmpM^{&DNP>JCM#7=#DmHVpIVUTo{px=ab<2F8O*bW16q^4k}kI?+en;ld4sV9qRR- z*;QW(@61 z*XzbBYIb%uLI9O^5&(edrr(~NU9I!epFq)b%mWJ;;38??m5O2V0jQ6UF6JTLH(t%Y7QF1_Wdv+E8P&*Cq;v}V?S6j)W+UT$Ux<4>OVaCI zw`XHbNah;73FP?hL9K_`>{sMy^ZNcyPx`(?O;#DU@6V5eKGW(uN~NV0u}goEs`fNS zT;FfB16mx%Hv!Dor%6Zmd3|8 ze7gzu>1{jhiIcb=X}iZZN2UwDQyl~C+KwKan8i_bv%yqF`IncK6sgvGXP)1J-e13^ z*{%jst0M?tyee|MxjGam zjiWe-V6t^E>d#fx)Wi_G!2`Ik)%tA*fXBj-{!G^y<#Cp(pGcuFfm(t57(^4zeh(h6 z&`MJ#$LteltnK(=ithHG`|5G4^N|2Y0mjh35=`?<`EkZ0$JH$g) zXZw4AL&24&I`emJ*H+H(YwPwDI6xHj>w!d5(bVy1`k^eWxn2i;6QxHyp`6Mz1%9bM zDD>-|`5lpV?;MfrhE6-kbjr2;X#XsU>_Qm*!80$u!@K$vs+6H!vH#6f*F=px@Xqi)oPiR; zgC|JFJSm1JyWZU%JtnF93t4An{$CFc_%G{b&o9n{Ax%Pc(;<4N1G|=1KRHY#3$)5um()-6SchtqrmH^=!Ml z34!;`XUc0N?7kc;{9+N4{YbGkGY^#RmST>l1;e{UK^1o>Xdrg)N+2U^X2ZVGTPuzJ z+{}+*Q=OsV1u1$Ky1g~iodNk9y<$GOu4h)UaqVp=cq0!S0ygKI4seDrw=1c?eqN8 zowoHo8UjAX5EyqJf{~Fvbhb$apDb)9D>sWqo2^DN5n}W6-?~eQs z*%tce8L*eVbcn}mw@6>!4|Z!3Hpi9N6NTNi=}KSEC^#@Nrkh80m!5CLr4XjJj}Hps zN3Su**&Z3Galp$JLBM-xc2wI5uMJOOSepbzbze++WOXWI5e-L;d#e>PQ=u{#pp{hBY2Fnx*Lnc5$XaaU=&GWqk5w+A>5)upv8McO&RjQ59L=5RGSDSkR z1LEyh|8Sc*Nsh=$iM%uPZ#7%05%(vRReMe#Z5G3d>blV`h?*c3s}d?{*^Fdu`#)Q%)OtyktJAIouJ}-BY4h)lU zO`rD!_c}){WVk0)qOUci6 zcJkw_-*r{jhwA&~%ShQf8#Ky9FwmLW^UJ-ZV}b*4uAr{gJu%TDT$1CBdJuV_Z}-D< zm0wbWA)VpRq9SY>Qu|U-PtOmK_+1er0-mg0-iDIQ?kyouwV_SXecM1Gn~Bh|YCElz z;GdhUEUA{Gro!Mg?CMCMY-D67S^{?8^<}vY?k7x!UbH5~KOa%GFTR1AB-a7uY7Tcr z%-UFA!yj+}MBnY&Q1I(_z}>wCT)Jse!D;0|9#OizY4)JHBb?=q`WUMxp`od?sk9EV zKqg8rV@4D*Mswx%esy)Oi2`dgHSjC}?dOSZ9MvlOwqwxxgP86%(>_Hm*9WX%P0i7e zwcX>c={5P2u@rk>igppk_uZ-ODa+trq2})Jh1d~ky9>6GC0Z~lE1y4PTdRL(dDF0= z)OYzHCOEB_qVhhT=|-v~W?JyTS?8HFbR7BC%Wzu!LLx!y6$lRQ@J`Xc#PmG~;?n9# z1gZ+ZS!MPcDH29qnoz(hZIzsawUjQQy_<1G+@&Dq^QjpjKucAX6k*fWj+XEHR4H_K zMmLg)B6o@y39=V1mo;W83$kP?Lr}zp`#xmlb;~wazqHCPqKms>A5-Jy(P43gZr51A zu6yHk!FuPU*k5r5_wPhorRn_lshqn~G7nfe`LbEt-0Y|5^VlMO?)RL))? zruYn&fW0E0R;stK?b+4GF%@`?;|kNq>FDfy7Q6raKE*gR5XQ(*!xq6m^ zuqh6YS)#k2imWtaC`v%66^g;OUU#zTvf!@Y6R=3-9od4j>SI3^^m)FhlsH=-yNLbD z34S&((RPP(otYUaa+3e-ccfOI6WMlewRxP>R8Ycu zQ6i4ZW$o~Qymq?Uln@N0%#a)nr}oyP_EzJB-3D1+Fm=~_De-p)Rc5! z%RMszE2;~A!9(c3B#SYX)`@9($nboxGP&B!njF)}qCCKkGyy??08Af zQ>){B0mFbTDN;iqE5W@nmbt?OJx212RiK*;hcxevgGscs*hWuUFYgC&r6&?O?F=!& zoXnCX-W=ySDsE?WLQe8cUHL-v%8fzZq`+g?nl^nawELh1uX;x^YivrH8IMI&QL^;z zqlV#l%$%>WIji}FICvH~s4%KheDEVM6B8Qkf~&J6Aya$7bjZ4&f=$pR#L!NmU@>Ac z>~MCfENUuTs5DMAZV*&Jp`9K4TWzeNu)U`oeY3Z_9>vs(a{TUC9L7dt2KtzkrS9!B zSs6%brh|LdkEwdi&cC0P3rrB|Zsi$mI-@BS@bqN>RPFiy4iznoChFDdPnP^`dS^+S z;#AIHzn>gsY98PpJZE!1sACieMcRBgmL;yRM5|1(EgDB1(33xC+<*`}qCd-WOlKuU zGd=+01`JJBT`kAwWQ%3ghgQgR-X`ge0%++5yUkg+usrSdie3A}1Yf0RvRQsA-LR(6 z{1uatgYRAok~POCs0;{aB9+g=4SYT=IW;R$uplC!w*m!ix3;vzYS?u-OY60YiFOY`mR>h;$#orG-k4^8)k>Q zAI9^nY3>Y=!sfI;BrO>NVXH=lE5(5%dW5jl znQpA*5r)SVJQ0QNgoow>)2X@E|!{RK++56*Q*gEH|||3nBK^()4-$pfG#xtE+lOZ&3 zpBL(kkyNf!1I8503d3e=j0`jgH9yohDmA)GAm&BsubQ}-XKc<}YmIA}LOv!I$mN!h zuGhx7=z4}k)gaJxd?5^Fg}t{DTfd5P2n6las?_f=0yft53=if}W%<~k9J5f{1yRds zt^i>15F*llKnM;$1(tnRqeLt977vr;sM|upZES3=fwAGB&Uz(lLt#m}03Uv$*tS&3 ztp102vC&YbJoL5UYla19)}1uFqjr&3j3VmD&3S3L1y`rm``sUbns7pf&LoB3^+9hJ zt6hb^tY^?gPe${fp68lO!QOEc4pmg+Y;Na)KMa?eX;z!YkyDHeFs%w}h@>qP5JWKQ z&9cB&yQ(vIy+Mc~8pD;?%_?`Kw)$x=aryL%!;3x?B^?&R7|PTY(%b5eM1v@S@npT?@&@X6T(AGxS%Ko+-~s}HV^t)q;W z34-8pl4z?VPPRcqaUv9XBCo8-{s)zdwaIADFX)%xQ;c~iqdOAmsX2y!+M{2zWQ^X} zXZ4I7@7AY`Bl7Vmk)Ge87F@_h$`NtIcsQP}3+!e1yuE2o-MKlxoSB^;4xMD{y3K(qJm=_6jk?xeA4_~-2@q^AXjvNqL`8) zS}qc~KFjwxfpx(<9j*)`;Wk=Pf2BgYyRp=z{m7gc`tj6aI1a`&(Tj4qc;& zVB(OI15qq$K-lg)@bV#`3<(}0I3!v=COJQ+u8Ty_R=nSam??^BDW%b!FOU#L6@@frlboUa7e4h!my%k6X5 z`LkIzknnW)0t!(uM4Mor-y3w0a#526^z+3_|IlJuzRR54Z|8!HSS=s5nH~c1_7x%s zg7epmRhd+SLm#&d{?S{8XH01UR-~OtR-I| zY$chN&E+b(zkC1iAg2*Pm0a4%qk#BRh}GvP&tJfr1%*VNA3Q-@b{1tLv# z`&j5t;LhY*_6+G+3mwM|#;lIZ7vTHi535^^g~Z60M^6{Lbmai;dI>a$t%TQNCw{JF zH&Tk=q3OBU-_Gxj(e{`8==^6NGz9^fUX3{qG9dWpcZgBkzh***aXgM8NCW`h9yK#P#^9G$p$02)K8*RhkVi;kBk732$4;Wz_(eg% zT_b$_G2kU@Dg39@N=0s_5w(!b<1bABg`j%^_!hSTc-55>jwwZ@IFw?w+`qECGtcLr} z;5ZZdec8*Bx+7}o4HNAxsVF5i9`%(H{jK%uF&yWVAb?61FW58J?&9HH=+U|3)ZFpF zIl?L+G^sA%K_H$ATQomGKU@hZ*FxA-Su{z)o{+-8F4)sSM`xQt+D?u-M$s1WC*sdR zF@7WZtybF|GF0tSK4GEWP+(c6tiA*^g{D(!(E$+7bjA{F#>UO?|^uVfB>JsZXW?2w6(aH1eh!+FTQ5OS<W*Ed@uNev^st|wGSc>S)4X_y6bC^ zsmOREB)ng&1#5FLND;P;a#PJmBmVpb=B+NQ9UUEmc1MOrAWMV7Mu*54#gv850Q0sF z2;d>NZ-Qki`}lm)O$s6&1u-EV-f7dv;v^A!r(8ev$3CY6mm-8gU_U7Y0fPNuU=i$DiPUX7m)bsGXTbDz^RWh8#YU9I4A%s-RCSH_%igL z0~bO@jMi3GLne(P6SiJ+&TY_$%PFUa(92I_t9-kK?6Oi35)vDEk*+RH4*JHBXwj&s z+E)tQD=Qd*RN&oZTs>xU1!07r**9KfiUrnfDY=Cn4CV1benEX|gmz`p>FlRyI>dz1 zQl~VbhtaErx-NabV}>$88ewr&0|Nv4c*+u}n~AObKveJRl08!3UzUN^5Pq)Iv0_Kv zm<1341LUoQ_&?MHcAMbfBK5t!e1di&_syC27jXYm3qXWZXU>et`qVd*^J5r-Qxi;E zy$MowvSIi3O@iTKJR(-Gkkh$=97@e9{;!Tv6`Ju{(30tLmBz;*86QW|wfYwpg)YK%-FRA+pr}iBWem$-g1jO5YRgy;vTpS!4 z2?XfmBhi<)2le?9{1l|v+%O+KgG#VcT;MzvtV~KoG94nXKp8VapqiSa%p{u(ph_|# zSO|i8zCW7O=yb6r-d2xu-=lEcux_{<^PeiW`w6S9A1`a_>CYx={&>Cc9HGT^rQq#+)_{wIx z0z1+z5lJ=Nf|if_iB{Yr3ZSNlcS~5`)HI`R~DND-5c(5%+$*UBstp z#S>@@?c?gSoQnTKWMT6Ce&2DNW?8{+pum$dFC^j-dix6;WOq>vXn!)>nE%fz(_;r! zBQGi#xvKfwsorFUK&4g#%t!Jn;)*{yGOLM-{sC;c;yoNs59-llPtz!3{kJy1`GF{U z-jhBdyfTkzBj$c4J2#u)G--MZPnk$vvYz96fN@7Zht! zcv#zsxvAHk-6Ft)^-q_Mo+}a}dAFzwr~!HFlB6d6Wl{W5Hi{1W+g{`SwyYqWq3oq$ z6ttUG8yC+Z8>3s(Wz)^0Y74sKBQKJKS29r0Co6KVZ07|XGlT~kNNzK#@sH{#wA(XH z{$F(UZ1g;0v<}TlWIWvdLz}%Pu-=HDhR^3CW{*ZTT&qVX0BgvwQQxrxB4_~ou8?&^ zkJU_pB9sp-U++ijV1%4Lv1IMnR0a%KciR^3Pe>lkbi7Y^vhhH4hSAi6tI*P;*cm<`vm=B=2=yl;3@6S3!bh6vfg@rWgTmseIs4(-wY?O;~C zX=!am>)r7cw!9vTCu#?v@tIt{Km-{{4(OwVm@$!d{~5mP8bi2-dEWv`FAYG_a7N!V zF;X5n>$!p{m^qy`Qyz-_qv$zRAjnta&<;w{Y$9{R0p)hNbRpA8N7V;ev6P+qg8lvJ zwq^6v0Y)?W&k{t&5C76>9O3dg!+$>Q*G`C~@1QO?q?Z6a*Ejy^VY`HtGYIS#gigrU z?lvBs00$P%>a_1#KqfiDCXwtM1KZ0ngw3kVLQ3qa!bXf(p%@BefgD|NSC^l%fK|-v z68<6JpHgEEr7r=KSc#XYUg+N~<|XAuI`zKGIrFjDB4eL%_5mF#9$F5QLfmNusS$dD z+~^zu42HWBqJ#xHucp@2kPMTmb9M))`7lLn*{ZKk*cE>ot4l@&jRcVim*&80Q(}iG(HWnS5_rsv1A+=9w^AXXGJ*T|Gbr$)aYU zS%a*phr3`9c20%b>Q3MMiG@VZA#}vF=Etw>HWmos3xZv9{gpS>0?N?@qz94+-7x`A zAsQGO#s<=)c*3!{^0x*ucWqs&ewcQ%&9;<^V_t4Xl8HJn15tVtWe{@)PUXQQ2%pyM z<#kGr$KWKBVFPxn>9=>S)t!Gse)VBiGI&>8pJhr^V{)toDn2Y_*(y|JFhvQOi82YV zjh>cm3kVKy^BuNSPxpi)$fSnL-b<2DdyX7;Zvw?Dg{rveQF!Lu`T+bb8pAw}4B2@@wW1)HFsyqmIM0?iQJqE8sfzJ&;BRi zE_oJ)u%^T8`{ZOZDkk9lXX)0yRBpV||^l zzAX}sE=Ju^2#`2|F1b+{3(fW4PxN%s#Dwf`Xcus^3m+Jb3pW&&S_Pg}XS zt-H@dt`p1A;7_`QNz&j)PF9*iR-Vp)fL9Cb^UVzjcB-VRD0c`F3770Y_}Gq2`5(C} z(Omdqg>wO&u`SQEUFYnIiimYyM{IXxxekjkK(}g99;S1TP0mip#f8QrwE~2te0Z36 zUiPU5WPc=gWOaafYBc{WR0u?yhsNoT;kZQ18+1987MxY%@Jt2WrX%l5at-cha?zsW1*L3JML{)1KX52pTmC~z5tY@Lzre?w;Gu9w1%4g zAk9;fYK<_?ijWK)biDSkU|?pe?ZMeB@-EkBha%=2Zy1=@W^z~0x@7$@p;pS>53HUa zb44a4NL03lqpV!zN~8>&d>D-uZtsflxR$nLv?|mPsGmS5v9&PdipzBlhsU4M8VhC` z#|&~tPuhk#gh(0OT=Vs3W_Pf147Px5)^i&*w15_yb0%uN3U7Sl=n=f_TSPMI^Snl9 zYDzzR{p)a%*hQ=?#wV~-gS#$b#iB5cvnA~ieG5W2YNlhUstTYtUQxtIhg*0&lz1u> z_zdkdudWo$(~_`1M6#)SJ#zowUnm&fQ}Hz!+p#Z4lUJ!4=I z-HA+60tTjGFIdfdmMdGL5K@9u9|!NQT2?&JkfbeO3{t8lPHW)rYx_w~VMhmM?aF)d zL5IOMhBP)u_Rt*Cn7G`~Db|qj6bTs*#!U{O3FlJotn{#^m9fKZ2e5Rd`6-_p`(IP7srfugL69AEYcxP!{BI@4p?F2i(iYgn=bj2($P#{=9G1uwDf6 znMh#^FK0VXyt9|`^zPEXhe_H`ySv+rF}qyI(2>OFR?V**5ut1$s{1eKh`a#J;8T=<(GjOhIz!Ctj zY{rn@MD7Lq2KJ}E{UZp)!(hK4bTgZGyAni;&0FBt94U^2DH{&~j>QSSi*H=qytJeR zvt~QCt}oSbUw#}Yh`(+LZ2`^fK zYIJEwjvp#6j`&I2?k?!PLa=8(4x!v>YzhSL^ zL0hXHf5(~ys#xjt+c67Ca?raIODYDzYk(a%tYdyl!F)wSdi8oO=yTY|9oWz7r&x6c zuI~_&PeTz5=Zi0YI!_xr?|u()GRCJNU&K;Dz@%!Z~?oT@rMY0~Welui&qv=YJ>~F@P z8u*sR2P}#2_l`Y2HK3%Flu&BD-&hWaGa;XD|jzMGK~xO`SK@ZTtAPae<)azR^q9v4+<8{N##9^Uze^@Mn5_ zJ~ZoVfxQygWQevtC8KcV3m_%;x(d{4e+NG`4$(VZwDhx9n7K1 zkpX^_fqaK#MT>c=74S@D4Cq8DM!gF$WcWhGYDBTjh#fEn`X<1n5%>HDAVYS zjubcwk|7LHTV$g~%+NTJ9bl(U6GY3l#kILUc%XlI11O2=QF#bV>3$h(lFwxrgon=; zra-(dkNN={(2nz4+tZg>G1d}Cutn{J+_G7#0$H_teh?JnMy_EKa0txe%l3lj!`o!( z{G|M4&u|>(**Rrd=-L-DctBPKIoD)H5lFJ|eF0vxa8}gPlJXl0wa#vDAWTG!I9ib$ z89}NBe>PW+|A}uUTSW7V`kpjOG~m57rltCc2fB?T6EDMTl1>TJC6~)K8&Y8)q5WSnV_v1$L;CN zh%Dn@Mbc>jb=vfwBp)&tTlRg$%9V*b&3B9@j@v&+r%seV9dJw%5fZO&k=p4AR`7WY za9#pHy<9X~Zb;LZBJSs{9&3E8Wb2=mPoosloF%QmT-raN4+i zenQeH>~9z>=5rqYL4DM_C^I=a_t1Vbnh;BkPc{;`ZZK%(yag2sS|Z&ypoy<{ujYxb zcMI~#XGBD@ep}2W#U|cum}fH^J^P)!9L8x>=)U9P0jwAcBaX(}*u{rUO^uU`9mmGu z=t9gV{1tBtbc0E!j>(^;0+xOqQt>3ZrQ4Vy3;C1N)%2aiFDXZ$8ihfyF8T;bY< zz*tBP45<-tm<~~QRIp%e;oBucWhr9Epab`$V37lTHf5=PvEE%nDodhhHe+TB$gram zA}18iUHl^tXv4PoAfG#PM$p);gEc_8fmimbQbUhxuVUxX+1$n#2u2j4j}o;VGGZ-= z558l6tM@!IYmyY~!FOo>!-9*w{R5;HuJ?zqVV?nm;Xev+Ad%hhJGiTB@y}%0Xidw3 zyoG|-fHtAM*F%R+!Z*DiEToADD*uAFOa`yu@|lH^tQiVn*6KZef_D3S;G*@^VH!$9 zqc-9ivj>eX&-yKekdce!FB^up=*?3>cxbO);d=4f5<^QI-l1yLu9>V!&39)`4YCPvmV0@z;nJ%I7Ve5$;G{>2n_0g{S?Wv=`qK zcuhg{QmtgF4=bVw8kwJ0579DLr#}nqv zgaXntT-{T3t9^8PT2_neT2=OR7XqdS?4`lQvS=R zK|{iH8J~+|dAaA0?x#O zL@*^GKa>gx=i%SHb(~()z3)K{2oHkg=XHcgSoDp27jwXrW@btJB5rah`Bw zDl6ROL`pf-Veb9X%wbf?=F|W%^T}>}4~_t5et^McLJWaP)_k#J*h>i2U5FMd?Z2KN zD_s{^!wW_LRP!z*RZKDX82V(!zAQ9Zpv~yC26b}S`5z)|jQ1{FJk7A0YMm;jcN*^Kzds`tG7(xgc&Bd68(GEO@m-ZIMJ z$^q3y(&8->%E=!!#!~!W0GdE$zibT&rHreHW}tRERlY2LYpzQievNRu;ou$o=Fd+;k1D1&weeSY-0Ga02HBDji#Y*cP53QGp1Md=uQ zE$rOcf-feM!u(XC1xfr2!7U|$Zt8Z`&~0}k3X`}nQd%*R!gL*-J2tbg(~2kP#~Dgs z^3*Y;MJpI+-O1)n?Re#K6tW;*R|mG_S_Q#jPUSi*t3(}-Chs}UYVJsnq1Vb zTgRRbH@e(Wj4!m{>m8tbzz%Xf$(dSudLS_)hl1=x48A5-ukR){KcC!W4feV!wzl-) z_Jxsa(;1T!rhe-x7S}5&DafOIVky4rJ;+NZQkEHu($&e{jn#w;%1Ki9Q}ap_)EV5QV`) ze0n~b&h2ck+C{&inDP<%q(^yaukFC2QJ{}Y#3T4vg^F^{H6E4DqUq(W_j=1J9tNcPNE2sGL)zl z6&oS5+E~r*M6BWNPDm3Y%}i)VhN2r160HCd-ZX&rzPn79_cp7tTxO;BX5+nTSOO#( zB0*5B7twzJb>Dk8zx&>O_uTJ%=UfROGudc3a0h+*O|C3UX>V)7?=(W-B2P@HIN05c zas3>pR*FcAa@00IO%55+^+ndgNity>NqbC!b;@<-*&zj}v`v6sZ8?Gn*oykjl%b ztc|oZ`!KQ2_~j{fN+pzL8xCg$@^pmt;5Nx*9+|NMZ%Z@v?h5p`z@h+fFAYxT@#v7@S?wX>6kN(r&x z3c*m6ctM5TQ%75K4HYKM?go7}%;NYJUO6{KRAQ#}&;goUS|W?{1h=E)%8)^ezoCWJ zdJoC5ml;}1kwS~XQAcN!AD3AGsL!(Nv`+7g}U`X_II)h1BH4-`q;IWRuzD6*i*6yO<@OEMu!_p}nmR zeQ1QC)iPFJGp6n9jBh1K=aoov5kgs`IB$2<=!nb?(LX07UgW5ha%9;8w#H7Hnro?e zdz!RdAiJ~1{PZN#!8lo|0!6WiN@K=T?}Ai{R9;JUofn5D!OX-8(Xxq}+Deq!5DRn5 ztZm20rHiPlYG`h2M6)`;*wPl!f*Os@igG7J$>gD_yB)7C!$SWx7BYG~ZY?{januzK zDhM#Xv_qyK(CahgWEN`M+Ng7@iLXvGF||fC4N66kqEd~ys+IOS?>jPvB8wwT%+8CO zcXFc>kKK$iwaLb2h*&N~N#&uoxeaf5hMRL6L^E>Kr3}er5nXK)?H!F+b7AHuMp)k1 zAzM}=k?U~QHqca4fi$|z(8w}7$s$Ix4!tr>U}=Hbb*O4+r^RQaluaWu`>-i8gx025 zi1P zvyWgq^N&0!{WSm6C;UivuJY|~&e8r`k6^g;kG!D!JYU>jX7J>*%xarC+*C<^XN8fW zNut&F@*6D$`mbDJUFyW5DG=EVA+a@c_^zYWnRl}&<20QAK-W6%`H-c1@1$ z;ph0~fRrQqI%#cdWc|7S#EY>mjvuJUS>?yC4l!_JjLmd`(#inWw2$!be$CD5z>9qE zw2TKHJ&fCIVD;P+j2ArE4P~;42+@>|?mG^mp1;7gEj`V>jhOQrOkEo%Vr=2A`<=|6 zpCn~y;z&D8T%KS{T}idK61~!Z-C@Hh(2QkB#1=9H?_*FgbB+^Ys7dZ&~j8Gwyc>xcb6H!j2w#{Tedc zGu#|qL)G=`++|+i%DHodoCoQy*AbbSCWKoA+PU%P=@&D&hv{uluzdC`6G=7R=6W0z zE~*_uhV+&QLq5vd_^UkjbeNv|4${%y#MH2BVsz9PBicTbtnKyn>ppR(z68=9bo><^&Jzs}^_Mgri(o1n|kn_+MzipJYQW0is6^@~J3CpdAy$>I}_v)z6_M?3u3lo8JU{Wpo+@tfRT z7vzaAT@?XtdwzqfkAImd&*!+W(}l}4&vRc{p!p9U!j~Q4nXe6^I(UE{VSyM9u(({r zceESB#w6o2(0*S7xtURJ&Lz;Av?!7xf;j^Xy@xo|;udL(<#LwAv(IpGxqzjvp6W^$ zRc;gE{?lCFmQdsOV%Dq41p2wY?&Q9Y93=egUvka+Cw%UZ9lavYz<2%wBej3bC)<^5 zpZhA0ulP80v>lgLMsj9=siK{>8Ydd9AVwr5u`n!Ya-gRkyHQO! zy1?+wIW}S$;`29I_J5Sm-`&F6(|^IqXdAr;TX3q&Y>Z!L!*qz_Ek;(ZJjuHMY0b!OvQs(lX3It8JbYs?i}xchh`n$R>i=Ql`X<19_AQ}fWLIqWYo@yc12 zr9PT#t;qB1Ob(2)<9UEzJ63;d!BHf#G{%jK*V#1GP+L0V>6;rnQ)n4 z>*ljOHmu{W!<{tL>KJ?SCGrOz;b@BsMRJKN&zz&wcY+#qfUD0=Kw}4;ek0{*fU(&f zWI`68m}Dmf=7tt(H3^m%BgianJe5jf3xf=YOf>f%W`EO<1Ke^c%E04K5peg=*VT+m zo8;<0eV>iyyE)RDHkSj7 zR)^W9p%fnH<Xn5$Z15_+M!FO-Usp;vW z&SfS&d5Ou09F<1M>?MdsCHT9$ab=g8Sj%GY`*5f;tWQj_nzqn!&oSCwrzn@mgw|MC zm}g}xL~vyjnZ23bgT1I0PIGyD3x&6lI+udL&?F^KH~VW~dT5HU#*MG45@SBV)mLsH zJ@j$z+yAQraHG*EufF;!U;XM=xpe802xKi5i>QwL;DZl}9=U=5)xf|2o12>=__bQC z{N-PMi3jgGPH<_RjYx=`u9~{Wc0AfN)2DVbxUF5iv>5{o32mtruwINn<;IHvv z$p@JWX0X<_QCps7U{!{rp#yttjQ;ruCTBHXyOz}KHC|mdaK}9-IM{68wKXc2C~OXM z_QC{VotGMq6-8u$(UBF@op*4ktqSS-3^OZ1HnxRL5(U-my|^S>%*_ZH+-5pjoABzh zYzG!u-q<3zx=qR8p?&`Wn!Q%ELIp)JLu7TB(`TniX`Qt9`YEk0vbb5m;&O_cNM+R z2TeVFc%zp&GcUo_vX9EOmpC^OL)X|tOI4ZqE29*vy4l~TVy=IPO=T5zH4apu^f}*Y4#yYCWzBFuLLRC8%2N{o|$4jugC4FM3r1&dT9r#vy;QU z&3LUpPHf&ByTIka8B!)c4GmSWu|!~Xi>y+MMww!BHHW#emv+-K{r!t%93AvDIFW^> zmKdFBH zL91OzKHLWKdQ;OqfcyU9_Fm5VkO15#xi}j|hKChk1Dlblf$~yNne{)U4NAEpEgUiV1_x~SGYkr+i-_wLc zS7K*roWYwbB+5lfp%Kok_3*#_`F$8xU*d0{Q1IJ-au25D5+}d<4d(3osI^EaWm9BJ zTB;ixVev96_6Pac5f9q@Dg&o3Fr9R9oQEQX4^u%BAUDfaLKi{X|>UW-F%~4NNjRT|EjYnVD1-Ko5#2uzlPCmOv>t{Yj zpIt?IZG=nb`q`=Z7$;2g+_*7}r2P}z*un0 zzx7eNstm}BDOQJ`;fKQ>9=_kr(yKpU)&DsjIcOj{bD7iUleGT&Lv%@3>AyC>infoB zAFC79Yu^C47nv>C*>}f2s&}sNFPAoPwH@Yghnv95-{6|;0LS)wS$*npLO%uI9X49YZnLa(`)D3?4_j5Ff0C$X+zCDlUQ;%@Ww@Y!nerbTP{{Z#*MW*Maw4ZRY z-hYi7%Mv_pGpbUWNLEj6M=yPCRp`XJydtapXBb$Fpm2F{+FW?6G|W8p5?hTu9Biwn zLZhS@zQkkyI7iFxK8*C0|IAI_pYqv*?*MQw{CA#D9p_UIHls@}aOH*TNDe>5v3fgN zxrB0TiJKQ+<>jdYRc;#^NtSq4iL;>*w{(Z4#V`t|8;8?Pm7@~tFBQNQGPpLXYYl{l zrb&8xIo{Wd(@-KDm}GP`NGz8p7aZg&ckpMA9%AdIzv7bm6FhuRD^mj&Sw*I5F38x^87~)lJ9nWM-KTrm)u5qFkS0 zJ#V6}qXU11?)7@tHvq1g`B%qLH?-5)=EI^kNQklC<%ytpgv*sXHb zhpsZYS)!`OMi+ihB^6E&n#?*A*Uu7m-OZsE6UoIJ+!##YJbZ{QpMjmR8;ma}sdyW}bvb1$ zE=SQio9JotV-+d=K;$MrdVUFOYX@B|UUVz3@b#;_Bfza(`XP@m`8am>LF&v2uK&yT z7)qO|s4$}@Lpm#^!ry=?KF6l6jqa`ns?<>yhHfytP{x1k;OiOOaxO`9ZGq{<0KsTV z%;qMIP4pfM*w{d&QgQU?QU34`|ByTHyi=qo_xJa6>eMOmpTN=* zvb$gU(wBJnBM(r{qzLTZsAP2J9s{mtaBeS_=lhmgP=5_VrL-sU|i0BHtM((tYsI8vs|(IGK5s zXUC*eckH9L)X#Xgy>F~NTPtf^KRO0MYfhENLO^xQSU;N zSZ01ILWO4GFO1bqz-Ot00oCuTT(W|o|}n(lTz3zsK}sRXdwghLQ^T)V{6 zr?;@T_foNOk{e+UUB~aF)ANQHng89G>*xTrB_oG-k&-sNCm5qHgWNvVN zQTjT-y&BX}cj!)feM;s|evg}3fq7A6dMSpZ^$OjwVxYFUl}5XS;Mf2oi&2bT2j#>&1Kf5`o~f~MrZ-C>ZCy*6aL!15M>qE94TgeB zYWCkjUyTvz&KRf8&LOF4rq!}R|3s3C#tvFc>zu#1E&^P)Y>weIC7#YhbXYSija+9Y zYsO|&k_v`V`2@PR7v=UeL&G!dXzJi?P)jwZtPa z3Z`223yec`lJWCrnMxUHK75qs$~}PlUI6C@-R46AaGzl~rp4dhjzgou>=0^9G{{4j z`TF;RbUt{Tu9`|R7r)H6HxKd9eQkIR3S`-BUi!PgX8peZi{EV6=F~s_fY=@PpuBty z-9x{_3450Lm;Qn0bic{x+qQW12ZOkN>o+-Csid$q!<94V7}Fi%5l@)&=Z2AYoS@sS z6Bio_okiSfHM}M>Sx6Du4zazq#OmTKYld!)^|n&A^#cFj+W_tr=NI_(165d*o1FdT z_gJdFmxF#4#f9hi?-#52lYjpZ*0qyw0=Ub(^3CtC+4>MC>dd0ro=mAhk&iMraFLDb zkMps8l_;_S`d_`kOvcTTd*1-K9i}<1&J=KVpWr})1%s;Tw{zsh9-{j;gVfKCIW3*YNq*jNy zaJHXF-N(4Yw7}5#?CT;^p^;a(x{$=#_YjW2i}Z(Y0bG}iY-F48_9{z(C1!#;_8;%1 z(PI8VmrJ+V0nf4#Vj=kTS#BM zMx?QuqaBS@XjK$K=XvaF%e4Q_L#WUF4X5;<=L>iHF)AaR``UkHuKiE>^$sPWOW)$8 zb?&D6Url*XUo`@uH; z_|ao*pZw39H$KXvch*y($`KrWo@a(DI4%I(!RJ`_JVdK5$mq=&O?Ms>>2%rEVXj=A zAmKhvm+lRbsV-}T$%#eM`3$Lo41=SQPC;Bx824LWgEs)Kh51)TP}FzO)9%NjRgju{ zp2uE=Lv=Cw7v(f{H(@vC8GZgqcG`cJ2kOIIJh?#4J@?S;qrHXz(&)9+|}hp6`SPrv)54^x{G?vCL?Dz zsJibM?dB{?1LrxnD5IgJkt$)aE>oh@Q=+pC+fC@Yr>R=XWr-9H7IiVCCusrs7(> z_A<+BNwn@(dfWWibV6PHj{{u&`Z>ONDo*>+19aCr$PfO2zaKF1(fbb}0Nfviy13_9 z2VUI{R~~}d#&3-Dha-?!43JPpaU1vqp$^Nc-oazX31J@Z|l2CK}Ag%6Qk*Q*6 zo|{ACtmbq$-4;@dL(He`H1{3Bx_*v{gb9CJFU@WP>zBUEwUnQF%@(6UHI02ebk*8W zXO?;S+s_bgx$jp3;0iUhOG`^)-K{`P7J#czYunn|%J2XF@AJt|ev+!HDx^{=i9~|M z#YJ9umt`lf{Zgo+EtYX&#?4aPY`Mw3FZC2KzX8um^ACG*6C5 z@wfNVQ)fbw++gzZIVPn(Y8?_bM>jBZ_tH_VWPA7$7iM$V{q@v1jmTv}-Kz+ zUkA8?h-ZA_B+rj4sO{ZPPmPY;<}?>i_mgoouAHT4KZ+ag0S30m#@t9#0+bB3-xtAD#UNN z29wo7V(di*LMCeaj??K@A}gktyL_6dycStHjYL;LV^=$s=?PA~25=RNjE5}Lcl6O> zj59xajmd-#lTJYkMwI%#jHhLtM}&4rMvRU~5>q?L8->#UG;2SS+W*tgUadwZ6{Q{3NrwcJ4XSOI`L7UwJ7+U0ppb z-7Rdr@YnRacLA?_wa1p|diVfU6%}k; zev+|54F{SX$YgR{n;FdOu z-89%GWJB|ePi?$TQGPdo>kGZg#n~8gS1w#CNz2 zZE&3NX=uN{k;2q4BY^@opC7wUfl_!D>_TRAH`7xt7f5Ud*$8d29tyzADT*HJPR7OJ9$Mk*6<^ZfK*?X`?j#GEZ)G@TZ?S#@5Nd z;$*syBOPA!iXt2H<0P&7IoP0Q>GG2VeII9EMUH+5aSaQ#3~wSY3rrMWq6Ai$Lj#M+edcd3Zudz)KiIBuOPH|lQmf@2dxu4 zd8I_pz9uS+1;$UkK)myR;G^vl<}RKgpsJ_Vr9+lmXLfXonCAgL`8q|pL{<hrHT|&88Ho^@EF9tW_?|stoJ1b8KX6blh|Fb&7H!G|8pQH(5>V z@p`SKRz_Jax@qk@itSwhZi`3B>gXJ4Yb7qb6>V;vfzyM?4*zOUlp~Rd2v)!Hm9KE^ z+BH#0+3j}o=%bJF`OklzmX;Qj$~WkkLMB(p@Ls-rS)?%^Zn2Y+Rgy#SYnG4;j53yS zaPVLshS?wSYS4nKu8Yo!Ek*`s+0nT0I?du5U~MIduC@iQG0yC@)m?yFYa~5CO#j## zd9xdLg&_E&!(_EnY0;LNIpJMTWhp*K;KGwZ{gJ2yqr?4ZhNLK0qPa$*fla}U+(Fw?U!tZolx zS%jtK4OF$q>2gX5%@2srSaqd})Yd#}aWyV?CFPwtmJ?d4I|L>BcLBJaPU15|+?-3H zuXJFys7VB82<9qj@9joD@i_h2#vcQ?ujk>6su|V--$an!v&qlFA0!E0gaV;9gI7X{f0rGCfXM zTY=YYK^j?QW-@@X`7j528Zl;5l+_w^2CWFj%eg3PGouX8Y*98>VmHc&F3qu)RO0P! z!;;xzWpNv4q)hKdTH~X4B*tBINYQZa8 z{rcKepeVl%aD7^$GuP>#3?Z|+MDb9iQisi9M;*MtOIKIO+ned|n<<2rSPaXlZfV1o zo@aP=6Ro2fmq|e)6eJ`t--ZKcL(LY59Qd+h=vF+o~LhAfvwReO+womH4*LZ)ee zAN_C`S@);-XqO)HN!I$G=Z5+|9%)msxpbXtH+GQe6ewh6N-_mhv~zE(p1{l~Q?u(7 z6iSfjaMU-@)a1o@OSx9C6&jhCVR+f_XL8~ZTjRyhC6WfGQ3s@sp^%1tuVKSy4r0zqV| z(nnj5AIaJ*!y^;KBx>aH9NU37a%&TJ-tQ$eu}W4`MSH!O=r*$lF_%Fp_)BJ^>?EUT+8^ca$}N@xYba`t(&|(aUmRk3GmE*d zkLvs+gX43oX6>|hIFN+bSzQg2E~P2yn(6GS$72;Hyjv4PbcAP4C+NDf3%5zj%IR-0 z>G%|PH|bEya)bi|T)Y?rL8(?ErL3{j*V93TBFX&F1!BGj>GPC`24|TV&*DGPjav{~ znO$H*=I2n03*~DyxWzCt{nJDg71Ro%ERk_8%w%v@*HiB{vvu<&rX-Ct*ZVNW207cm zMz#b}m4f_Mh|1#+v(L4|#qUnx{Ixr2HmXPuKgV;k5{x!KJ)IIJUfIO^Ko6$GFwcH% z1ZA%aqeO~SCWtQxJB4~ut8**{B>48%V~nmaIXTNpye!5=)TK*pVj^AOD`XB)=f)DPs8*CXo)cUKj zY9*xRE^~gegr}>9feXob3GNdup}qnW_7|B(y14jg6`DjAeiHaa`naq0@J zj9emQKSH~s%+A71ZZ0H5rEjIIKuM*-=;@(B9cFDSjH;@WI)|3*>}Ag0n4wtR%Yklz z9<6+fmDa|{8Kw(W)cQT>^UI73%(Ek?XUiZXG0-Ydn)N|0otq^sksy(2Dd$&FG(60a zIz73-0M{lW6a;39REk`YqvY7nk!I&DA6!96c#XNSN!DU1GExDsHqcVcEN;v4oNK{hc(}_wA=j8D(iSg0;I9pH)Lypm9!4G84)P zI+!SpcB*R{sMf|=o||Q1BSlG250<4!Yh2XUH=+wIlGK=RdaJPtnhm$6m+Zzcmj;)K zrUgY%1;t1dbMFbdeI>@8n?ifAN38!-=9jqk;v)Lq7QB*8MyFQUiRX~XbX2wp0(w$n z^MeemlrTD-I1NRjAqkGw2CA%jr179A);7Ho6V-3YWkpev#^xFfi4~^iSJ_Mozg&;E zt%+KfinXB;mNp}(YkFz-S=br9!c-_hQFsQ)v4Wrb4>n`my3CchpRN`^4ugc)#yq1# zQ|t)Ju`)sB*+G3r9j1Jk+0ijJ1VwZKa22Ge)fLqBG~==E`rwwb+ssc+FtZR6RbVCM z1X%qvG__;g8Dk}*#qO;Wy`dwM=a|mg@p+vP7-2Ffi1kR3s#s7Z$LRZN8#sp>9bQ*&q%j#l)<&6mG zLWyEQfxV$g6iHGAuW@dCi=^B`ce@K^WR8)g9nymPgIrMdZV(m7jclnuZ2HrsN#s8)Zzc z8mhEmrbp)phLWPFraHaEP~5{ECysKU!FJ0Iu}mo&CotO2*xC*miQw{}K$a_FZD_=< zO|v|{z)l*Z0vA{z!&=irQ-z#FV2tr#0joD|Yvu;Z)mpuufH`S#*KP zc|plqf;1l`nk%CB_0V8ZizQ`hhX+Tc70s<3@aXb5^IK_Djs|KRdL-#JX2xe&4#p`; zL87uz)7XF`af4HX0U~lcRR$@Al9CF4J_Om9RC%{6-u_g%@*f zo51n{>!~8bZbN)&14(5)Jv|PRI|Wo`A8mfqZQE<@0o?b9R^LanUp&A?@J@@7MX;vO zBjBdjk3y2|oje+&UbION{v_DlY(&$DEKVBHLCBe8j@&`FS#ie#c& z#AG%qjWSWfKD-_wQ<5N8$WdxkqS~5JH;_-oiH0Kx&#quUq&1pEYZm3LMNv5$7bVE! z!dFI+GOx5?)C%?y86um4fI<;%g&kEUL`GGCS*t);uN9)}1fy9>Wsqq!NDE3TTqfk@ z9MSa+;w2e6t3ZXx65HG&E_i$(MWHresWgdpHPP@kiL4+#6<}<1nWC+SdyZElOXn!b z1e*~xl1xv=l z2UN_Gh)0PecAp#JBWp65(5qkb(JhjW><|%bjEZHXS_?LlPPB?ig|~^OvWU($!b03` z7Hxtgf`!Xkh-9Hi85L?x0jbW3RV|^A3=_wQMZfDSSj?q~hQcHTTN$|utCWLjl`?a>Qdnn_}5-Y?dj>EAL5LM{;|QBtGT zXv7VkcxZ=2I!8fRp{fL%EhB10o}Kj&3Bl$C6)JTBna(cO=}HQ5wzp$s^Cct#ZBZ)| ze9kaymE!vkWeS-jv7N~7m@72s1?v#4aIP@EQDQkcI-LrcIQBwcRN`h!F)hqHaZ|EFmNX z(uq6@gJ7{EM_SC0PDDkU4q-eb3JnIMNql!Q$vDxNV5ucTB|Lj74O(TEnZa2~MhCSG z)#7h`{nJuRu@jDpF%&F+l#((kvlW9H5?e`RW~2D-ONILP?KESB^Siyv<77P1eual zoDbCs8HMBy$&wPadbfl|uqsLC1e+EuT8bzPW^t~(wSV|NW`JMJDeu(>R}hJso}T90 z-~Kk=|Ni$ynzCS**W2667ryWX9)9>?>~_0IO@6mSv<#Az$V7I;u@>yU%D2ARdaV*k zCdPIoM!KlNVm0m7OGa+JV^YDQ&4x+PIVfgGCGtqMS~O|}vQnN*La;GOlP#2x$kk{K zg58tgOTW92DdkcKwnl>GP+r)`mI%*+5u;v>ET1GH;5YI8DUlSu*`lRPn(dKG4CZP$ zba+4QZrv?_D@+|_^2wdu=b*4_wW5{@Ha9wy@)D_tU_m2TUdT|W<*2kKaefo#jYuLd zs?n+ykWHn@2x{vx2@1h_MJLRSiZ^_2g>PaaBie6iM4P?>$=7VQ%4Nz59X89ZeN`?l zC_1aq>h!4JZu4C#l1)a4#D#fDf?TEBwOK4>DM&PEG)g3b^<5-RIxB1x%SE<`K_jJ* zO^Vh*!j_a!qN5S)(G(@|-INNk7Ji5j!%PX8N{gzT60L{?n;>~EM!FUo6XtQ10kg@7vM7w5Y}d|AvAYh- zg@<_g*->(yUXCAcr9x4rluHv&<;C&`r6jj|jz)`GUL==JkuE7vs}x8JDWbyjkj@YZ zF0iy2N8_!>s(?bOfZbn1mBY+ycC&>{f>f@AR1lR?$&r!Yoi|0B#1dtxP!456x_E|H zCR1oU9dx)k}h-tz1+{PKjfp7T*)Wdn2Ds5{o3r zmE|baf+NW8v#XGlDCBdL|&YMzUxYRM0!b&O~F5K4F)VAJwPl3&~F*U;E{5GoE zc3SE@h>l3__b8>Tkh zQDJk{(B9;GiwMmJ)unto#`4HzX0{9B4xmhJz|qi2lh-T?Tix!2^+EOfLvf#Sp2W@^ zLzg2|-Pt4BT68}ab@Lm9}P&<46{CQC|`OKL! zqQ|YxW)rE&zx~_4EmD(>M&oVQ;C&cFnBAIVYGzSnpA|}3q z)zwwhYBh%r9pZDJ`y6-Obr&9wN2DwNTQ~$uop@ZJpBKe48HLV_Q76#U_a>;H<@lD% zIg-&BiDZ@{5~O13gPiyE0l;RRj347_rGR{#FDKT@eF8<1_a9dd+Wz; zR7!12##IzzrFtPzD0I^p-cRafHu#87x91Be=aP2kTi@;t4ex4EdWdOK8&)>gy?Y#)> zMc{*vz)u3W!UA-8d6_4kctWhb9UmVTJ1AHJ3AE&g9(suC>S|F~N~pEpJN6>57lFM9 zyw?cq0o?Z*soQV22XJpcW_xGsMPM%iKc@)n0o^$NKdnQ8ML-}@L}PYfH>&sv0+>JT zQome(FBi$hg3JU;c=VwbzMRr}~AIe*7}T>EC)E-wogj zLOK2Y{h}q%i!Z*&*47pVgMqv6zMJ3sz26fl%7VJJpql)_0^Ab$be!#t5M_M@R*Mn! zPpG}jL^lb=GDwBCqHDqJk7#ylp4kvoI^EdK>f67--Whul*o(lAMPLu${`?+_4*|f9 z6PR8gCa<8%=f?DI0JofBb##)fyAiMD!>2S_&d>1NR|c_t;vTwPg2&hU)`8FIq~k;xdE^=`p zkFBMvkQK5tZ`}8^I*L}X} zfA`nl=VEENh8K_d*!zX;#D^NMdf=;TLEu{Zo0!nV6K@Vv_WFD#``x`Z#`}ku_XH6Y z7o(&Z+tgmFk8~|BD+?o>eX9mv>3&L5q6ogs0N1fN!_@Q~geG7P4d99b4?AaRJbi{i zs~tmp0YzC!M2kGW8?}Fad?hYd^~zW)&C>=_Q{ssb>DD%Awm;+BmY3UN0+$oGV&K`+ z)5DWbKFL#0Jw<a!KN9+svCX*<`BJF$SAjKojP zJuGr^w++ssiz^^IDOyQyw%)@r(Mj7R#Kb2kY5C+K2}lB)Cm;px<{5rc4Od^_iX42) zD|l2o3`%~p%ipS4A+7!W^UPO%fn6Eq%MQ@|hw0DP>~*&C?EhOs^n;(LGBKK<4G-|V zcw7$bb`QEB5wY;I9dR}O{_i*y^HZry zgOZ)w2kk8*(1FTRVn>T^_OST5_Vy=7dL(YG}!xRLG@q`SL2H;n?)-OWb28#dit z5`su~cQ*(M2+}Ft()Yppp7Xx?CpIp&xv={ZH`WE8x4JBxB~N(_uc z_6FQmJj$@`6|uDWt^1+N>lFBxgJeECmYz90so6HsFkz_D*AL^4S_adsAQoCkNlA(4 z)elY2%Na1g`*AjfnVOnfO0pQb&}qO%CXz-388%C2eL9pfa?;JC__pf1P zRNW6LD9!AR=$6mRFS#4ns^3ta4P5;n-R)D>UWolPYZbw6h ziP%=RkTHUwKw4*{d#+t1xER`zeY1}O+H0v?D%o|E-Ptx_eBXmg2RuUrO9)6HCL_F? z8JT7-K5uhuQfIW$#b+XlEWcpetM`2ebqN2yRQh#lLn%8kNj8Gj?c)e9ESbdA;wyi? zzifsbrLT0UJXyDKhMD?7&bUBM?$6`8>BGi{6_4AuZ%n(yTs<+Hw!oVicH1sPI1X|0 zEnW2mCRkD=Lc0{4lEiw4d3_Z~R16{!f8VnzAUjATszNzDGDwL{e&$mPtwdXvr*#H? z*vT;n!a5(8kDyc)mIm9R7jp6}i(r>-w@`(J8@;b!TlUD!wI))o3iNPA?)o#n+vRs) z3Oj!Q>>-j+8p2WL-Fw|9N6$dPKHax5PAB}Dr+$V1U7%CZb`e%hBO&v$IQVY=n9zKL zSS+9}j>M90|5mV65!uYII%y>z5ii>y2pf4st`UQ6UcBUYfP-7~{($*A5^U>_a8#k7 zf()BomnFI}G<1fPgrf7KqEMS^bc4?mm{1#^H(*&NOofTLIc}qdLkn+Xv|D-^yIIL` zubA@a=uMPZil108b&n_${D>U1NtwBd$sfkfrC5c19~GHfW~7P|b5cX7jdF9Jq~})n zi+Ay~^fXLIm??zEm2WcKJ#IVRCPscApg17ac42hIY3H zM@8O-u!e@WP7<4`EEpQC$}VitSI05*umP(k4LKXb!EcC8`*cWZb46niCtVP zqL@xNcAD%FzW4j?fH1JCuuFi9#=2L(p!*AP*`3$iNnl4{iJPR$fT$mWP?++LtT9W@~w%`++Tg5r!S%!z+fl!C__TcHjTWZTF zh*?q57`{r4rmJU+8Xvc1?!* z_(NpflXJZk$WacxNysCL9N6ESjr0B#HdrRVZG*3x4>q-njPE5T#ca+sXT+B5h>X#q zA#m2?$RG2)jE=4S%8p%d9Vn9icgrl%v~T9u;QqmhrHh6Xl-p!PmXdlV+OZ%_E-R;{ z#a!C#Fiz}2`cJ!soyUzaJs>DJ~|RLN4J=k zp3sK`V}dU%ot}mqw_9n{WN?+w0)rn7d#+l*rQVNB7YX~x8lc(hUhm5VZmB_OOukic zNmalS+LkdvNB25@oRJbUC1X>zuIX+mzh~e&0y191o2%~#!MU(#el^)1)b=sxO}>#o zd2|nPTQGvOzo6?W82Ny?DQ9|HLOQ}>;$d41`<=v-SREwk}*=UY@HI>;P*rU1L zj?`1`k^YYrNmWm*P1seM>+eLyrwmn{pWVN`ziJ;Sb0ti~4rULNN>ojbb=#oeK3SxD zkjGppCXD4`!b-B}u>x*2!WR3q$b1NOVxOLsJL%{6I>(2_SQ;gr7Pc1u zN+L;Nm|N-mxQ1sgFc1XVLzpTrAS2&W0Ub-tdbgi_=S1oE%gr&rJw(V_Q`Z-q#k8O} zTQe}|Hy$nVvP252L7U`pVYfx1RmTtF7fD@9!MuB8*MG7MR!+|fFH-@a_Tk^t4-$TtnEIm`? zG#T+^zTXa~`*};GqN0NETz#CeyT_@L3uih6-mtWq&Hav7XqHr5<`atXj9hwXcZEJ~ z^HSueVQBTN+aD#bxzheH56D7ljb~p3hxLcW;*6^dS;pVuu`{FHYh-E)B>4>KO?TC1 zxT+Yo+gs}v7KmgF6rd9+^rV?3J}s;s#U(@oejbHSP6CVO_Zn~xzKok=%0iv=cVU|? zS1YO%JIWXRO07W$StsiYrFc6z46xB}B$7hAb3WfLC~$dy9~q`o2UTA6Q?~P(lmC1t zV`hVvaM2OX+<>4;S#6&%0@0E6Vq_#OElX-Y-S;B1vIz)y(Nj;~blwO%bX;i+ zaBIJFX!7oZb#GNcSeDx%9*d6oCC9QT;}jOosX}UrAw1)RxrZ-9@X@la;~TX8^V?qs zQ{QVoQ?m_2y{t1!=>=0qU#sI)R<1?Xb6)i6*Xtb`eJ`W#HX~;d$#}2J%FH|w?cIlf zK5yDoiD5VLxrhqC?$_w)+AGRB9y@Hcg+euYV3}*$JGpgI+lPiTb;}MbjpdWyjD(*Y zeV{%oq}?6P-t67_9W@QjoLQ#Ge+9g z+pbQr0`XAOHEzH2`#?q~CwpmAQA925y^rQB zxxI>F23e@rHE~5iPv67p_sz|_bJ)6XT*)N*>+(C12y(|g3M=tH(;CvF_Pd|a=MpiR ztyb{&+XykmHma#DMVU_-P=wp?*(&9Gso>3c347(Fjr@GW;>Ma-|E!Eep?8_44cnC7vDf|s2&_gzW^N+5R2jruUavTcJey(*&+>m#lG)5Dtgx* zwULt4K2up@G^C7#)o1*`H${3?doy-FjN&@$_W;V_bj!v*(tCfjQ$@NQwmHY@VN)K$ z>erbKQp4zM5&sj{KJGF&-%`Pb71NORtFMaFp)>TiIr(@)&aPfnZ1(RIGw=Jaw#3p| z>JnjPNq5B}sh8^^Adhrnl7V<%r9VHoZ}NZHCbhUeWdA8{Rp{;#L##KzGne%0{lrO z4fD@>28geLp6|(NKcb-ZQe%QG``SC(t>5lT5quC*!KBwtXa;MF_XtU9vrGc7M_RTq zGEk;hRsvZ)S(j=}BiMeuJvsVZ;Ts=OLq5o+R`1Izc(I9y$5>_JJ+cM&^!NHzPw67g zZuqqG;XBSjsq>3OC<;5<>MdHhAmw617s_sFo|A_Y5&SiNk_{r|aCIhlVOPiE&$4Yh zCytz(G|5Ph1I>~x|Lhj^O{{9r?w$1!r)pzKNb(x>uSxx)B|;ge%&?wV8J(eJ&>Ogd zMkRJRl!27euWfB_2Nad9VKQo96z4h=l4uip3w(NIR@aXL*&-Db*wH)2$CCIDt5|wn z?@HQ*b9$~HQ_Q3m_-?*uEz~*|3Yi6%?v_)f+0(L$VBDWdEB`>nsJT#fGxL{UMtJsh z9LsD<@6PPT{V_Q)LEo#f)o$Y_ z`DBd(PZ7LUr57zN3l1n;8}ysUt#NS7u}O_hTAqba@qcN@j#IGmqJdAO-{416;Uy=S zhm{^{2(*GT2oEf!vLvo1_0wr%Re6gjYs`p3_u0Sd7_K+uTSYc$V$EHH!07HrhE<5GBlb*m?g6 zrz6DW)x7>xwaLnFdKAL;?SE&z6y1Nzo9n#V^*htuxfbfKXsO|geZroy>KX}dn@A5S ztC$}-d%oqZKe^-WRBOZwoa;MxVH`5x3x4{^8ZUImQ-as%{8e#cuf_C|TDUu3h|;Se zEn-J)t`P+*H+AgX3Vke(3}U#CM&Y=GCa)CaS5SiYYAISQ6Md^H_7IE!!!uibKm=2Ohn zx-8WT3VWpvEzN$z-jPLp#cXkf$Ev4H@wtj z^`jI<0sPX|D_6&keu<|Jvo;pUx4wS+8}%fzpq26t$w{Zlc^-~toPq0(P_3YPrp9+W zIcWIow{m=wJkxdM)Eqt0a>$jqxI6781z`re4G}JAhYXa$Gzblwuqsa%aETf@{t5ofx6kimk9o_|@d3o5;n)AYmt{_IigjUhbbEaYl`3vAky8Z>o8WBMg&6SZTZkVDSEs-(sd&0Y~UAyCIh8Gi7u&?gE@J})y2 z=MVhbw-KPJ`O^jD+2mydCBOHKT~EO&`Zq_^yeug0t8Z6?MMGHlK)6vZ*?%bZ1H((_ zvUgkD9h3v3e4|rS4PJ zslO(1kMd#ZS`|k`Witb>m>W?&Ig|s^y8k**mou7$CaQMBg@RbA{d5q4eV~RZ*LpC; zP|p0rn;9A6j*V z(Bd0D@K+c&seX0HcVJ2IZBvko`dP!ZBKNy(_k9}4X9@)*y=PPvk+#fvP$e|eXQLm{ z?{>^kRZT5;!lKWP$N8k=h9U3^5;Ahi4?QP4nYFtecxxLwOUOv5Oo?e_2Z?R3{jv_O z{7@Y?pF(4uHyF=&(rv}B7S7j6r1Y1;q)Pe-D+2|u#?Xcq{;o~tS&`=Z+D7>KT?=cA z=)g%5nF<9ym;8RJV$6Ml&z4q%N~-jsZ=LwsVx5iWq@`qd5-R1w;7hcxZKhdJr-dvF z%ye;`>S$Z6q9{wsRWlZ}MQ)f_ufQFwki4yE;iyF7A7-JI(|(tpzkIE!2zT}kk!YHV zqI;z%kxljWzS<5Xsmg!o#A(jck4x2TH9(LJJm^Yo-2pjVU~}^!GkpD(D&5?cl=fU0 z_PNdB%O6wNwASY}|3;io&yqiih+MzoFWnwJ{}t{L{afY3{-LmZEmHE(l!)&F$x+!- zt^Rwg?a6C(qM<#ZTN>Ba4xjbwcLlytmvcF5;{j32qbNIsSp?{lyVFmwdwji7S87x# zhlS(1ePxgNIH{T+vTf70)rOmwH>9RkewbT2E>N5utZ7PH!Ed(Y=fxY#XHCW4 zCd@yc^9)<2Kb$Zq*ijc$a9Uc>O^{Dlmh%2lFkg1oq8~|c)Z&pSV2RnJxtoCTZ9rs2 zZs-FrlPTKy9yf6AeT`eLU|kwTj~5TIcy>jUS%5L@kemR;uulRZcA>pHY4W&ay)Cozx{eZ+RbDJ#cbqxo1znB85 zC0^1(ZnZ>TC?*AxTeUFn2PXyfGO2(ZY2~Lo`YEnZ-uk?3fAU`)(JB8Hn@bouNv=GFWP?6T@x?M8TimmKFxb> zg_>Urr%TVD81!j8Vz}>wuiN+q<#pjt1pC*ZEB!8PAH~0&3^X&ZT0b`jIXvzs@tt># zcB0@1hg+E32jn^8cKUqT~p#PeGeRtBluDmHzMv^aisZTsCCXHhfWqhOMh1 zW3Bv79NE(2FvAH6{a77z>?lG70UJIes*E45A0$cn?du*+>o(tW#dfO-0V226M>0VsopG*8e2T)ZIW{(4VJkEwu;!2 zhfUZfx9IK=J9%U^1n2tG4blNA8KlW#uR-Q7hu?-Nd$vsXa#k-qRD{s|oOZz)5d6%% zUH1kWH{Z%kp*j-p6Keyn!jx*_d)dq`**+JpHuoJPt-}pPCEl1m2FnW81DR zxkCIiBvbbL!+cJ^>}YG7oL<(w%VAwh6%M6(v-novnPrH@W4N;}8#*9D$SN@Mt0Q>) zv!~AF-0W_2358_&nj0rcyo|Oro>TEFE?_I*1l8`U(3G7O#jSZue^#{BGH14TNve5p zOl~Q{xkBWyN|ec$b;MlaiB>9ztz>8S(MRomxK=B_E74{GAEE)6-`V@9pD(-7keCGU z+H5=yq)68V(NzfxEj>*LE2!TpNU`;9>X6Kn!jMCxK{q=)7Je65t90~%{kxw(lf68A z__V?L6!g2zosm8Whc!Y8X*uP%uw6?qn7=K)fQ!=Ju~SS}jJxR&;mc+V=T`=X(#lb? z{+XWjRo()67P1LGDaokF`beb6#Md>h^Ue>7qG3V5)AW+X?XphEXkv;>?yPJ`OtekO zSL(-?zatG6{U8HW z=V&Ptie z_TT*~KKuj~`D(%w3Etf>=f5+n@4Ba*Gc$9g4;2*3`5KBy7r^K8V|lpE@ZSmvDyr6( zhhe|Pvr*gB(bvSoaz>+EvhlIUnc5KQ-nls_%(6|34%WK{rqs~eNy9f=>e65awu(l$ zwbDSkw2Oy$)rSro^UQe|Vi6k9r$#we_DtM9QWqXOto64zr1nXz;bS|EXcvLUXSH+{ zO1MMP5x9S@WBNG39WJK^+P0^4&hTp#TUHO?SJzDk6<(pBCW)Y!h~~?Lvgap{wrjn6 zth)F!LM=-5|61W_Ifp6r^+P>aH29lYqvjFWz`VGAABI~>;Ab)BKLJ`uV;5{!?Zaxj zng}t_ew@!e?1JG8_C!sZGT+bUPWZma)6kCx4F2p}eB$c7Dtz>(faoJ**OU52x?9ZB z4UbR`)k;`OqE4z{HZvxK+SM&s2`u&ZC!(7|F&hMui#B{XlqlQJ?hu^5_%lF|3yNy9w=Eg30(cE{EhhWHbhCNqSiUiz8CvF~ zpb(XawwGaf#UEb}g%yarn7eB-K4_gLo2B|}D zstYZ=dx*ZqnvKF3oovv`rt4+-S2hsghedqt`c`O}0AxgZao4$;ja$l6fwyorX}n?& zA!>@2etNXtH6b7ld3lSAz%Jk04l8a(D&9G)asa~c%`Kj-NhEMd28~}XUrYC3U)$n} zBn@9%=Mozibsf@G-;nicXg^h(g1|;p zHHcotnc&lO(}=hTvTi|z-9=24xAgs?@SBH%W1;JC@5tY(?`CT6i%x|)2^z$q`{{5+rhPLJ@x&5uKDY zHKkdHM~VfLq>*QC^@Er?KeXDafAlASFp6 zPJ3oJ3M)OPD~#Bea>8vohp4G0K)u&s@xiHQp9csCRSd+1GQS|c@z$)y8C=hKXKF&9 zetD=Bohv;UHl%$Tl9)&s;b;L}c@iH?TQ=E9UDG1xF#hBMMLa+sn4*Udw$Qru``O+^+a1eFhnv9=#$dUXIqVG^oDosZ*T&x1GTShP;0lCmyGA$TG{k zz9eRaA72Dhnx7?fTh-2=M+_RhFiB`qYIc!2!s(k9WpC-f4(=0kSbzM|0AGsko>v| zyllwkvaAAI~xgsh1kYN(v-$ zH0eVO&x|yUG<`lnJwu4@)x&$_-Ed-1C~X3Xqml6^6i0QIk#&O|+J35#$gh{6-eqT$ z*ok%aOu5LPoV?3JYo}BSEUX4W&MOfeT|-1?x* zo82Q&i9Ic?M^My6A5&Y$*AVC_U&JsYwX(1{ShQ%HnqkJN>Kb;=G6R0TiBI10glyA! ztob*LRxPJoL_{~=YTxZKBHl9Q7G7}yp}iReTim!7cA(dKqlmn%Bx@a`a=Q)c8#N(* zZ3-kucw%e{t4u;AF$@_=uF?o`zKSF!!VS4b(0ZdX2fr;X8&AU509+cG;+irEXbd}> zs8Farwvh?O?!bCDT55s` zTB})-4+*Vv2fj6tuF*i53^svG$dnSq>y0r3<7@k1En<;59by<7;dQ4LD!7((qN9A< zUdgZgXbSOB`!nRl_AK=R#0I`pHBL@c+7hB3=)`I+SCgg)alP+wgC=>wjtVD#9z8oe zf=%d$)58H zSCdhj5a_-l(+G`A$jfti-E&C>zmC??Oum^S$dQs)1sz9d>kVQQm=+-5u@m+Sc%NYA zdN?Mg*UC5U(qP(5Z@jZEwx`eD0wEvgJ4e6ekd28W(>gG2J$Q!=vri@c)+wg(J3CL@ z?}NVWTK4%4v4@KLk(i&xL|Njd%Kca*RFn+&|5$T(ch~c7zr=GZ0c>Ss)4TmCAwK?d z(d)ZG-cE4~3mSpnV6{2>QRzj_Au(@O%A^av4jB{}#=UXvq5;ATds1m!BCwAhpVt)A zBk+g}GrJZAtk&B0F;*SteY6w?B zJrAC}8y5}ojB$^L1^ipXOCuECLPHWu7qgH>b9}E3k`j@~tq>0hjj;6VbZBiqCT4jL z_l$DM4=6Pc+HXZh(-kX|4e5A#e9mCv@vjBYShn@7<6=vfOt?7o`N)`o4}z(Y_GW|O zYq>QANSxTjn4|YFbC16&U+7i`*fdx9th4tASgvT89$W#!H;p58u09N?D85 zipwfws%9v3c5{GMe>-2fkrS~|X#AV#L@c~+>^L6FlP?D1!qqRHoeY%9Ez)y>DC zorIT#Ja%BwCML08A5nSxYR#z)^SP**G6JSdiyH6#sGVVZWzL@ht)p352PFkzwaW`^ zQ}LuKqL8-~aq(yyv8#4Fw-=(NmMga$EZ+jZ?P$PC5{KR}%)NWIc2znJ-Su6496nGh z#B}&(fbJ02Tym2qUidNBun1l2F^e!pIap?UfH@@LuYC{F&%7NU9-P<`ex%;_-e%yb%{vz+;)9UGQB z=vSO?I3YcWd?AUEttdLZ`}$tLO>`y+Y9VJS`Bn~{ciRx(_3-3 zQTt2htK6*0&@SbVKaU&T4y940_nn9W@5>gPlxp14`x-Y?F``J{oOz`9QRa$MQ|Pya zSZTY?+%)SUNpg9)*v!B$m+c}eZ;Ujw{^(qPQ?cfPO2zuf1jizR?lAMS?ESP=gO$j4 zR%t<-6gS|wQ=Yyp_^h((dJ5exoXx=2*N_bF+P2}yM`R|g|D^1s(F1*;D~d<;*TbU- z-CdB%emLDt{q+-_+jNIlth@(9bH6b1J@>>dzCjVUS6*!uw~S(|@7J<>Ar?JbUwJ(h zp)WE~^4t0qk~NNHJjxIHik-LpoOC-{6+Q-a)V6N#J;5T0g_@UnH+kUw;9iTWl{^XX z0@oMOZ>O#LW$&b0;%>O}(Bp<0e&KcE7K1ZOg$0y)kJ{*<=(J)ru4O*`{=Jn5S?!~P z71>R{8n`3vrP?(|-AC`eeSI0Dv;9)~3P0dDGE(yzyMCAJ7lGV|JgRp3oPHK(J@2)oRqjaEZwT1v_&*xjE4$qNAS~qZ7TPD zcmPISiAvs%;nTKZaJ#?{6Qefjr{nS(&5E8bf8j!&zm8uV!Uumi#m~ zd-3flfz?r+$(&}_#X!24r(2JE#<2FIS z)(kziSB--z>0$MVMJiUa{mfY|XvCP%qP@FG#!RD2A|Grzg@s1e_-nF(mvNY+5 z7&0AbO%yvjCdMfVse0qxhKZJ!Gtz^cEx;l7VBbck2<>l<0AEaAIpvo|6pIM%lp~E^ zBtem8e5t~EX-zLS>f@$e{!qN;RNz}S!IF#N@5(;9r4NDkbNP58)9ueG9Gs4T3t05& zB9D==85X&O@6OuY{3v zUP_oDEy0{dw#1FXe2U)q<+ z;f{w&J)HP0-tO&Lnz&(>$cMA{D}J&O+Bqe=t<;nBHC!2OkOXS>=Y|1PCWvM}qI-^l z!`G)xI@`_}#J@iB{ni7_hcf;@fn`(GwWqg#d+iOKegk)nvV`-Rh2u<>wnKDJJ1f|A zT^$Xcw~`_E4{R4Vza^_SyeU{aJYgxS=awA1CRF^+v8k!!Wiib-GX8!~>nv)fiF&1G z!hMs3Mh{_Syw^OMg@1#TQtK*Ck{Cb*;s3YS!EpH$SR30jn*e@P{R33g{oAIXRzyw= z>n|V7dM1-9ETYr>C}WjTgAO|Z%HeWLQrB%(*9XgGB z&yOb;+pPZDa)_65l_R!HMco}UUZFlDa%*+}5?;EYH|%_y;`8Fgc<7h-R4kP}+JV#4 zRwu6?ds4}Y`_CI)Bowx(AQ4GmIDTQ(+h3>xv*H6F(tc+H__s~|_q>yNqE$s8j;x7D z+w(qjQAGoT+}cb=xMQUS{w+!c*gSR^ddMUELnd~!NL5N$NuDL2vEWBsxPCv0zlu|9 z^aguTI~RiA5vRxAlRh0#)LH-?qXe*OIizDCht<4@0PWez;I#ZZ)^Ak^VzcN_{{chMs)o+KOF=j7SyLn&^w|b zKA|=J@@{rxv^qz66DbTqcvhDLs5)~7_>EyhxA%AIPdD(5*d%yCKkI_@x5hsPL9KwQ z&umWQ`HBdz^BVrkH{r_4)v=oQ5fRcha&t-i?q>7Ow~|yi`1vE0OA5=&73C1?EvHB* zC@2sR5CFFnpD<$uP@OIU3^ebbUDerr+w8C(4DO0elm-KUz= zpX=6kB&?O6Q5=@^#kN#A9XqcHtFA)d=j17`_ed5+6Ym@~N=qY@`K;so?#Qz%l#w~P zFkDUGR)E!27DHNQ1~V`wgQ#GP+}tz(XXicN=DE1IkijZZ)6z0J{JRG`B}KPFgAuOs zkQqSZX~w^0+pj>c^hq`zB9ip7xiN6@CDYv_8-L-sq~z^35^Gz%$=t($oi{Hmu}*An zJ1JrK6!_D0>cT^p>*jcpvZ4Il#i6#%FAIoQ2UESFk?{?>M>9nvqq6&+=pf)pF|1|@ zz-k8MblQqo@u14|fZ2U@y~O7$!{-!WKdN!l5+WkvdguM|CRp?i6AliJ?;Y9zvv%d0 z0lNl3#ql5>IbdK)un)ldG9l_ozw}i4@A>z{Zd6&Ztu}ytrQXvgV9AzmMUtuh`0?Z6X!WTg5o_3r ztG2nB4(=Fr;pseqD$q6q2+k+loH7=OrdRCwowVSLr(o$Mn)GJ&+SV*9XKsBk@tNCX z9&x}|D~h0clx&}r{f(}TgSHJsYyU%_v6<_$U#4JfSr(d!Ez{B?=BK^RDE~V*c9twa z7SqYR4IlOIkOg{ZhY3Vd6GBnB3}6`?SV>7qp{Ku_Le~qXmseNx{$nE}>*1mg4pVfCqCqGJy*?1 zIuc|YhF+-NY?-2LKT;bm5yzFD&_6xN@`*OLB@cfl@w+vQO+4%JOOr25Tc_+=i`Z`a z8xD_UE#MOtAe>jiCczBMy;PBoWKst*lLIlFYLuJ;9DaCJU0>fn#@aPL<#){qFepR` zFX*}lo^lPoW!$^Ejz(PygCV9Jp-@=Lpacx;NzM43hp&SnY69!Bu? ztmnN=t|$EF?^O$M{hR%Y!>(8=fYBO*0Ke&qfB5-wV#Z|E-rk-Wxb*e8@~}%^OY8mh zJPD9yyPgiZife0CZrUlN3u4YOxeVt0d_*jBUe z^`hnNgrlD%Fg=Dv&QDD(4e&*bqfc;A5f%04WSr)Ci~EtbM34jC3vQ~ud;z|w3s4{# zVtEo^vjY%Klmc+11&qMN%Idr|j7hy(A&W>A1crl$H)H3X>)DS951AoF9!YP}k_!0EOtP0!FH;*R_ZCJofo>S( z0Hf8XWz*IJ`lqF>{XrG>5x9Y9EysPu*v%~!SU#bqo{saoqyz%t?}J=8PIE1`9MFRw z|N8V*0f2wwKkC$?1;TwEQ4uc<U@SeuuS$PgnH8c zW9N!Mz{$5nti2g9>Bk_2n3x!2HUgl=5PeujUu<&Bqi|!z=f2$>24sw!V1kdG+!_P+ z%P3J_Jo-O8&@6`t?P&A|$`X%1r6pFto~$Fcq`bT;DaFp+TyI%fS+T+yJxpSHNEJd7 z(~CQG6<;#xo4fo!FJbgQK+j7a_;L@l|D)@`zw_46DGKWN1?T&h;Aw9cf`x^xOpnHC z){K_N15$Ay^9#=LdLOht{wK%+I;wkbk4{DCECfsITV0*nqNVpiMbml5iEkWm(pEx3 zqH>tq6Ez2?nb(*$F%C#kuV4INNclpM{}Uq;u+GlTbMx~TfSiN?QMs|P;jxz=-u!sI zl#-&CKw~8gVC^8EdQ2Wbn+CuM?q#x1*24cUJqsYfi9{GRxzdnQiH(i5n6K0y?OYN~ z!`l1Q4457fFufTtL;Vl4g#R&)AlTRtzIWr3lMDqKQi?0kX57EPA4ULw=mPi*>&0iM z|B+aD`C;;=N-&xeUBvQ2<0kfmN6nw9z zD^GC%$6_Qi(EyHp`X_eZjxoL*#sdSW<^In4t_yQ3y*yA^!_1EeDx1xDac|kH~swU7ZOiK{seZb2%egDxW>VHH~ zVs|m(Fz&JvWiswdPu{#RJ&cv6M~e7XY+R6yO$GA{*!(`q_*f_|B>5i%X`o48#Gd z+d`^5eEmYwSO2Jo=f9|@8&&#+AWBQ=gMxyf(a}Rmy zE&Q;rUjv7dz-2!i_Blszmzf9X83AJiGQNZmZqP3=cEA&_lyWH$m+e4$EciJ&IRMtN z@$rYQkCqToQKi4A+S4Ne`Ut0_ToJ4J7!hG<_UF&Rfhhcln3yj`MN*h8HVs~5xxh*_ zvm`wuklg5CqoSe!e420(;a1_owp zYl}`)*Z%NJcZYFnv=7EhrjCw@nOR_6doRK zZEa2dQg+K>-f#4Oc0RJ?bQl8wpQbOfQcqP)4f=T)JGCoWo(qMs0E5Ff2-5*N^*P?( zC#Rtao10S)3Jz}SzkZP~6SRy8AO`|ilTS+J%OoN`>G}EOmtMkx?#E`&PmhrczrTId z1K=}n{$Ftd`HhKD)*#UxX{1U~J$8OxTdTtetB{hCLjPLfOdDYZ$nFEu+RaFjffiCy zptG~H(Us*GLVqB}+gKO-1N6mf#ZlYeIJdCTRH5t%oMnGDkq?r$i!NRsS zFK22%65KWf4?WnGR7^}yZGK+wh1lSj}wGY9JV z6!BuH9fO4cFrl8p8uvsvJRqG9n@8FMiqJ-3 z7nyE_O`#cWAA*1zPJkW}!E;7A2<}yu27?)wX!}9WGVSv(X+t#ef92b5SFjj?J2@oO z3fJ2%DJeZ zzv4%(Hvr07@MhF|B`VTGV>hMq?p4i+VfnIy#Yq=Vvwx$t86Z8wsL2lH{QX5;U0vmM zJ~Lvrm~1F{G=J38WJHGPHTR@6C4GsBivJxGSQr$V+dGZc*1Lc2ST8kdsa%Gc~F9k$?B z@|zgZOI0F_lcB8_Q^K;?T?4ti4zzun*nOoWwm`x@e0XqBSY7?z=|kh9-Dq63unB}7 zbP|D0%QAq7ac#{B@OD-{?G$iQ=^ZSPVcKtZvg#We=wVJ++@WVvJ;Csc9p1b^nUUr7 z!UmxyYAUJ!Z5stKDGZ$8@r&IlMhCv$>8Ryq3g&Q(#@_CzHMN3hf_IUiJ)+;i=f<)C zn>GP#+Wf+%{nrTV6ntD-(a9{Mx)O$f z$Cle)j8x57)p1SO(a|xwAhYm}eFyWIrvK~TF6d_;fQuNDp2^9{vtVi9j*Cy_0HNsf zGNU#~MW*K*l8+Co35v&Jr0I>1%4=wEk^n{R1)E5l55*E^k(ePRs;;f1Kgx-uP(i+c zq?F~2y`@eCjCK2wB7m3|?PWo{F#~=SJNor2-RS5j3la2>d=>2|kgKnz6U_lqN(!iE zrAVv=PH*x%4S+JC%oIXW{mtMaDI5bkiDV@F?U#!!HgjW+yKr#;s&+M!0QCK%Q=TFJP&>tRe9S|Ry{3Lz}lpSQNR>0eJz5O@Kcv~*12sE4({0o&UN zg%{oz1NtnQ z$6hdie*0fM92Ee8(K9g60C-4UTw=k`=M}w%8Xg`V4$zrdnR6At^h|&!YVP8F2<40@ z{~`Px<{3C-7kX-d4eMtANh#W0YQ>%%hL+|*I{MYRd2L37FMzD?s+w-GfFc}}OHBks z(A10g%7ZcULPp8UPweXtuX41hhM%9UdBlPH*sbRRFcN37iu)+VAk#mi zz<)mB((4NLFewLgTf&$*EqHViE28tEM~L{wDt zd6boGCxP5DSFY*CgIOh@`vN(e9)j)7v7I1eNve7IkfAdxX%mk*S8GS~6Kf@E;N5+n{`QLos=2p87V4vBth zZ>#T~xX4em+M)Ww>%0{f_bHGM4Hoo-K-Hu8AY1VF?yj}0Tj@}4R|j{^z{krFMORplc8}w%Osh!e?NZwSTDYL(wVI{0zK2ZZr22)oN7OE?dx1o zCYEO+D{f4@F`3DHquNI0%G1+!yHDGw{#yNAPHeqj=~^`hg^i2X&YWjj<`eqhS+kOB z-ZLO$*Bnj(g%6V*+#H3hg>;OoX78AI?~Lx;~YJ+ehCN?iEznpf+KTdhu1Smom43`)hM|U;iB}vg6>I_lH6k%}khIe_E!+=j$!* z`*j=w68;Nhr=GoEYMn6Y^e;ZI-UT_52Xoz%UWGmFvaVd~`A717RDlai71x%JOCI_C z)7`jS=!|xf|LpH4YOCfY{#G(rcA&h{KgqZ2*4~ZjQ^fp&A6)bBVyf=CBeLVF^Th8e z(-+4~>T5nA!NmFFZASLpThkZpPyMn@H|93qwj=Y8%u8jzEwQ#tgoVQ<>cOfT!L2z@ ztv=0jpC9>K-C)}Rqi;(NoQ;vqKL6?O?N>XZrCTbV+}!cRec39;w<|nnC%igX^F(u7 zKGS2aEy_D`6QkvsqWkt(U0AgsH~#26-KJ@Z2J;V0-MHN9$5Y$SvF=em3F{rCtAj3V zEAQ=zG0?khd6whdrW0$ITz-49TEF(R@C^3_TsPmxeb?CXG41E~d2b`8F*I7T+?o-* zSgqRXnwRIBnUAY3dNGA_#!USlQ)=VB-8i-}T3PL_?#VoVyQ-fN{RiHQMbzo5uC=|~ zyz`G$%Z<;w*UPU}E(l4GtUh-{w(|VN%?Y+lpE+Y>tL0QJQX6+4nU-Sr>eJ?Urey9p zzFOC#)W5Catek&f4pY+kInQHPa{`0p-HpquYq{>kUHazSzyQpH90CdsAjWnsje`8A zTMH2)K&cn13l6+h&9#TC0x=3!H!ytw`))#sM=p)eV(n(#$jN{o`|g|NoVX z?iVupPmeq;-xc!lXY8EaulB|VKe#75 zBWFIcFIhNbL~ocjRhOt=Q$}_^BU3rIM!|crZMCTWR|5L~P>wpP|F;AEpO<(W)&F`x j|LdJ8b3zH!a_&Fe>b8>}XnE>0F#v(5tDnm{r-UW|J!#Hb literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/fabric-new-standard-session.png b/docs/.gitbook/assets/fabric-new-standard-session.png new file mode 100644 index 0000000000000000000000000000000000000000..80f72d65e9b617175d8c5d89abb491545b68a12d GIT binary patch literal 12010 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 E0BHWTQvd(} literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/fabric-zingg-jar-extracted.png b/docs/.gitbook/assets/fabric-zingg-jar-extracted.png new file mode 100644 index 0000000000000000000000000000000000000000..aa13ac8957d6c9823edc8a1dc90db863295739e9 GIT binary patch literal 117251 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 Mfl89K;>KV95C7bZmjD0& literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/fabric-zingg-release-assets.png b/docs/.gitbook/assets/fabric-zingg-release-assets.png new file mode 100644 index 0000000000000000000000000000000000000000..80ce57cb4325b268c9973abbebc80f420aa55b7e GIT binary patch literal 62536 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 UlnrVA3-?i0(0EZQXa4?w0AdeBO#lD@ literal 0 HcmV?d00001 diff --git a/docs/platform-guides/platform-guide-for-azure-databricks.md b/docs/platform-guides/platform-guide-for-azure-databricks.md index 951938975..9e29f9493 100644 --- a/docs/platform-guides/platform-guide-for-azure-databricks.md +++ b/docs/platform-guides/platform-guide-for-azure-databricks.md @@ -29,6 +29,8 @@ Uses `Arguments`, `FieldDefinition`, `CsvPipe`, and `ZinggWithSpark`. The workfl 3. Download the latest Zingg JAR from `github.com/zinggAI/zingg/releases`. 4. Open the cluster → **Libraries** → **Install New** → **Upload JAR** → upload the file. + ![Databricks Install library dialog with the Zingg jar selected from the workspace file path.](../.gitbook/assets/databricks-install-zingg-jar.png) + #### **Step 2: Install the Zingg Python package** Open a notebook attached to the cluster and run: @@ -129,6 +131,8 @@ df = spark.table(table) display(df) ``` +![Databricks notebook cell reading the input data and displaying the records, with the same entity appearing several times with field variations.](../.gitbook/assets/databricks-data-preview.png) + If your data is in a CSV file, use `CsvPipe` instead of `UCPipe`: ```python @@ -285,7 +289,7 @@ display(df) print(df.count()) ``` -_**IMAGE TO BE ADDED — match output table in Databricks showing resolved records with\*\*\*\***** ****`Z_CLUSTER`**** ****column visible alongside original fields. Ideally highlight two rows sharing the same**** ****`Z_CLUSTER`**** ****\*\*\*\*to show they have been resolved to the same entity. Tanwi to check with team for screenshot from a live notebook run.**_ +![Databricks match output table with Z_MINSCORE, Z_MAXSCORE and Z_CLUSTER columns; several rows share Z_CLUSTER 3, meaning they resolved to the same entity.](../.gitbook/assets/databricks-match-output.png) {% hint style="success" icon="right-long" %} Records sharing the same `Z_CLUSTER` value have been resolved to the same real-world entity. `Z_MINSCORE` is the weakest match confidence within the cluster. `Z_MAXSCORE` is the strongest. For full output column definitions → [Interpret Output Scores](../interpreting-results/interpret-output-scores.md). diff --git a/docs/platform-guides/platform-guide-for-microsoft-fabric.md b/docs/platform-guides/platform-guide-for-microsoft-fabric.md index 494e1360e..a143939c6 100644 --- a/docs/platform-guides/platform-guide-for-microsoft-fabric.md +++ b/docs/platform-guides/platform-guide-for-microsoft-fabric.md @@ -30,6 +30,8 @@ If you are new to Fabric, sign up for a free trial at `microsoft.com/fabric`. 2. Name it something like `Zingg-Fabric`. 3. When prompted for a session cluster, choose **New Standard Session**. + ![Fabric session dropdown with New standard session selected.](../.gitbook/assets/fabric-new-standard-session.png) + #### **Step 2: Create a Zingg Environment** Fabric Environments let you install JARs that persist across notebook sessions. Zingg requires its JAR to be installed in an Environment before any notebook runs. @@ -37,13 +39,24 @@ Fabric Environments let you install JARs that persist across notebook sessions. 1. Inside your workspace, go to the **Environment** tab and click **New Environment**. 2. Name it `Zingg Environment`. + ![Fabric notebook toolbar with the environment dropdown open on New environment.](../.gitbook/assets/fabric-new-environment.png) + #### **Step 3: Install the Zingg JAR in the Environment** The Zingg JAR must be installed as a Custom Library in your Environment so Fabric's Spark runtime can find it. 1. Go to `github.com/zinggAI/zingg/releases` and download the latest release `tar` file. + + ![Zingg GitHub release page showing the spark tar.gz file under Assets.](../.gitbook/assets/fabric-zingg-release-assets.png) + 2. Extract the `tar` file and locate the JAR file inside it. + + ![Extracted Zingg release folder with the zingg jar file highlighted.](../.gitbook/assets/fabric-zingg-jar-extracted.png) + 3. Open your `Zingg Environment`, go to **Custom Library**, and upload the JAR file. + + ![Fabric Environment Custom libraries page showing the uploaded Zingg jar with Success status.](../.gitbook/assets/fabric-custom-library-upload.png) + 4. Click **Save** and then **Publish** the Environment. #### **Step 4: Create a Lakehouse and upload your data** @@ -51,9 +64,14 @@ The Zingg JAR must be installed as a Custom Library in your Environment so Fabri Zingg reads from and writes to OneLake. Create a Lakehouse to give Zingg a storage location for your data, model files, and output. 1. Inside your workspace, click **New Item** → **Lakehouse**. + + ![Fabric New item panel with Lakehouse selected under Store data.](../.gitbook/assets/fabric-new-item-lakehouse.png) + 2. Give the Lakehouse a name (for example `ZinggLakehouse`). 3. Go inside the Lakehouse, click **Get Data**, and upload your CSV file. + ![Lakehouse Get data menu with Upload files selected.](../.gitbook/assets/fabric-lakehouse-upload-files.png) + {% hint style="success" icon="right-long" %} Sample data for testing: `github.com/zinggAI/zingg/blob/main/examples/febrl/test.csv` @@ -66,6 +84,10 @@ For all connector formats → [Connect Microsoft Fabric](../connect-your-data/co Create a new notebook in your workspace, attach it to the `Zingg Environment`, and select **PySpark** as the kernel. All four notebooks in this workflow call `%run 01-setting_up_zingg` at the top — so everything configured here is inherited by Notebooks 02, 03, and 04 automatically. +To use the sample notebooks instead of writing your own, use **Import** → **Notebook** → **From this computer** in your workspace. + +![Fabric workspace Import menu with Notebook and From this computer selected.](../.gitbook/assets/fabric-import-notebook.png) + #### **Step 5: Verify Spark is configured correctly** Confirm Fabric's Spark runtime is running before proceeding. @@ -192,7 +214,7 @@ data.columns = schema data.head() ``` -_**IMAGE TO BE ADDED — Fabric notebook cell showing the data preview output table with sample FEBRL records — the same entity appearing multiple times with field variations across rows. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ +![Fabric notebook cell reading the CSV from OneLake with the preview table showing FEBRL records and their duplicate variations.](../.gitbook/assets/fabric-data-preview.png) #### **Step 12: Configure input and output pipes** @@ -350,7 +372,7 @@ display(widgets.VBox(children=vContainers)) ready_for_save = True ``` -_**IMAGE TO BE ADDED — Zingg labeling widget running inside a Fabric notebook showing two candidate records side by side with Match / No Match / Uncertain toggle buttons. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ +![Zingg labeling widget in a Fabric notebook showing two candidate records side by side with Uncertain, Match and No Match buttons.](../.gitbook/assets/fabric-labeling-widget.png) {% hint style="success" icon="right-long" %} Target 30–40 match pairs and 30–40 non-match pairs before training. Repeat Steps 15–18 in a loop until you reach this target. Label until all field types and data variation patterns in your schema are covered. If accuracy needs improvement after the first match run, return to labeling and focus on patterns that are missing or underrepresented. @@ -414,7 +436,7 @@ displayHTML(open(DOCS_DIR + "model.html", 'r').read()) displayHTML(open(DOCS_DIR + "data.html", 'r').read()) ``` -_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*HTML output rendered inside a Fabric notebook showing labeled pair examples in a table. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ +![generateDocs HTML output in a Fabric notebook showing labeled pairs, matches in green and non-matches in red.](../.gitbook/assets/fabric-generatedocs-output.png) ### Notebook 04: Train and match @@ -458,7 +480,8 @@ display(outputDF) print(outputDF.count()) ``` -_**IMAGE TO BE ADDED— Match output table in a Fabric notebook showing resolved records with\*\*\*\***** ****`z_cluster`**** ****\*\*\*\*column visible — two rows sharing the same cluster value highlighted to illustrate entity resolution. Tanwi to check with team for screenshot from a live Fabric notebook run.**_ +![Zingg match output table in a Fabric notebook with z_minScore, z_maxScore and z_cluster columns alongside the record fields.](../.gitbook/assets/fabric-match-output.png) + {% endtab %} {% tab title="Enterprise" %} From e00b4528f732b8f29527669dc4a4f33cd52745c1 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 08:51:28 +0000 Subject: [PATCH 117/144] GITBOOK-392: No subject --- ...g-is-built-to-solve-and-why-they-are-harder-than-it-looks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index 16fb55192..efc6f25b2 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -6,6 +6,6 @@ description: And why they are harder than it looks Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem. And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. -
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always breakEntities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.
The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

The pairs are not enough problem

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

Managing addition, updation and deletion of records

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

The data drift problem - why a model trained once decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

The governance gapExplaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching.
The domain experience gapSome records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)
The schema problem

Since matching is tied heavily to fields, most tools end up imposing restrictions on attributes which can be present in the input. Zingg takes a completely different approach. By learning directly from the data, Zingg works with any fields and their combinations, with no hard requirements for mandatory email, phone number or other fields.

Zingg's approach also powers any entity type like supplier, product etc, not just customer.

+
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always breakEntities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.
The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

The pairs are not enough problem

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

Managing addition, updation and deletion of records

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

The data drift problem - why models decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.

Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

The governance gapExplaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching.
The domain experience gapSome records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)
The schema problem

Since matching is tied heavily to fields, most tools end up imposing restrictions on attributes which can be present in the input. Zingg takes a completely different approach. By learning directly from the data, Zingg works with any fields and their combinations, with no hard requirements for mandatory email, phone number or other fields.

Zingg's approach also powers any entity type like supplier, product etc, not just customer.


From 51ef576af36e62983c90eff53e2461df30275f48 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 08:55:42 +0000 Subject: [PATCH 118/144] GITBOOK-393: No subject --- .../match-types/email_optimised-match.md | 8 ++++---- .../match-types/fuzzy_optimised-match.md | 6 +----- .../match-types/mapping_-filename-match.md | 9 +++++---- .../match-types/only_alphabets_fuzzy_optimised-match.md | 6 +----- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md index bd6734285..4e715a44b 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md @@ -10,10 +10,6 @@ tags: # EMAIL\_OPTIMISED Match -{% hint style="info" icon="right-long" %} -`EMAIL_OPTIMISED` is Enterprise only. The Community (open source) edition does not support this match type. Available in Enterprise Lite and above. -{% endhint %} - ### What `EMAIL_OPTIMISED` does `EMAIL_OPTIMISED` produces the same matching results as `EMAIL` - it splits on `@`, discards the domain, and scores the local part using the similarity function. It is not an exact/binary comparison, it is `FUZZY`. @@ -45,6 +41,10 @@ performance matters. Customer datasets, patient registries, voter files—any do +{% hint style="info" icon="right-long" %} +`EMAIL_OPTIMISED` is Enterprise only. The Community (open source) edition does not support this match type. Available in Enterprise Lite and above. +{% endhint %} + {% hint style="success" icon="right-long" %} **Related types**: diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md index 4ac3ef88d..151aae1b9 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md @@ -10,10 +10,6 @@ tags: # FUZZY\_OPTIMISED Match -{% hint style="info" icon="right-long" %} -`FUZZY_OPTIMISED` is Enterprise only. The Community (open source) edition does not support this match type. Available in Enterprise Lite and above. -{% endhint %} - ### What `FUZZY_OPTIMISED` does `FUZZY_OPTIMISED` produces the same matching results as `FUZZY` - it handles typos, abbreviations, transpositions, and real-world variation on name, addresses, and free-text fields. @@ -23,7 +19,7 @@ The scoring pattern is exactly the same as `FUZZY` - their results are not affec For harder abbreviation cases like "IBM" vs "International Business Machines", `FUZZY_OPTIMISED` needs the same help `FUZZY` does - the strings are too dissimilar on their own, so combine with `MAPPING_(company_names)` to map the representative form explicitly. {% hint style="info" icon="right-long" %} -`FUZZY_OPTIMISED` is **Enterprise** only. Community users use `FUZZY`. +`FUZZY_OPTIMISED` is Enterprise only. The Community (open source) edition does not support this match type. Available in Enterprise Lite and above. {% endhint %} {% hint style="success" icon="right-long" %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md index 444aadc2c..f2133fb1a 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md @@ -10,10 +10,6 @@ tags: # MAPPING\_(FILENAME) Match -{% hint style="info" icon="right-long" %} -`MAPPING_(FILENAME)` is Enterprise only. The Community edition does not support mapping-based match types. Available in Enterprise Lite and above. -{% endhint %} - ### What `MAPPING_(FileName)` does `MAPPING` resolves variants of the same value using a lookup file you provide. Use it when records contain the same entity referred to by different names - nicknames like "Jon" and "Jonathan", company abbreviations like "IBM" and "International Business Machines", or country codes like "US" and "United States" - that you want Zingg to treat as equivalent. @@ -109,3 +105,8 @@ Dates should use `EXACT`. Different formats of the same date (`2024-01-15` vs `0 + + +{% hint style="info" icon="right-long" %} +`MAPPING_(FILENAME)` is Enterprise only. The Community edition does not support mapping-based match types. Available in Enterprise Lite and above. +{% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md index 84c82ed06..509a3d908 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md +++ b/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md @@ -10,10 +10,6 @@ tags: # ONLY\_ALPHABETS\_FUZZY\_OPTIMISED Match -{% hint style="info" icon="right-long" %} -`ONLY_ALPHABETS_FUZZY_OPTIMISED` is Enterprise only. The Community (open source) edition does not support this match type. Available in Enterprise Lite and above. -{% endhint %} - ### What `ONLY_ALPHABETS_FUZZY_OPTIMISED` does `ONLY_ALPHABETS_FUZZY_OPTIMISED` produces the same matching results as `ONLY_ALPHABETS_FUZZY` - strips numbers, then applies fuzzy matching to the alphabetic portion. @@ -45,7 +41,7 @@ Any field where you would use `ONLY_ALPHABETS_FUZZY` - street addresses, product {% hint style="info" icon="right-long" %} -`ONLY_ALPHABETS_FUZZY_OPTIMISED` is **Enterprise** only. Use `ONLY_ALPHABETS_FUZZY` in Community. +`ONLY_ALPHABETS_FUZZY_OPTIMISED` is Enterprise only. The Community (open source) edition does not support this match type. Available in Enterprise Lite and above. {% endhint %} {% hint style="success" icon="right-long" %} From 33f2c7719d47760b70eee6998e33b753e8190102 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 08:59:42 +0000 Subject: [PATCH 119/144] GITBOOK-395: No subject --- docs/zingg-concepts/zingg-pipes.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/zingg-concepts/zingg-pipes.md b/docs/zingg-concepts/zingg-pipes.md index 351c98f75..3ac6d33c0 100644 --- a/docs/zingg-concepts/zingg-pipes.md +++ b/docs/zingg-concepts/zingg-pipes.md @@ -9,5 +9,3 @@ Pipes are Zingg's abstraction for the data store. A pipe encapsulates the source {% hint style="success" icon="right-long" %} **Read more**: [Pipes and Data Connections](../connect-your-data/pipes-and-data-connections.md) {% endhint %} - -
DatastoreFormat stringEdition
CSV / TSVcsvAll
ParquetparquetAll
AvroavroAll
JSONjsonAll
Delta tables (Databricks)deltaAll
Unity Catalog (Databricks)deltaAll
OneLake / Fabricabfss:// path + csv or parquetAll
Snowflake (as data source via Spark)net.snowflake.spark.snowflakeAll
Snowflake (native — no Spark cluster)Configured via Snowflake properties file—no pipe classEnterprise
AWS S3csv / parquet via s3a://All
Google Cloud Storagecsv / parquet via gs://All
BigQuerycom.google.cloud.spark.bigqueryAll
PostgreSQL / MySQL / JDBCjdbcAll
Cassandraorg.apache.spark.sql.cassandraAll
MongoDBmongoAll
Neo4jorg.neo4j.spark.DataSourceAll
Exasolcom.exasol.sparkAll
Redshiftjdbc (redshift driver)All
From 91b91bb2cb0c54a165f525aca633fdd99c510d61 Mon Sep 17 00:00:00 2001 From: padam-prakash Date: Wed, 19 Aug 2026 15:24:12 +0530 Subject: [PATCH 120/144] Consolidate explainability and statistics docs Rename 'Explain Matches' to 'Explainability and Statistics' and restructure the interpreting results section. Nest 'Explain a Specific Cluster' and 'Output Statistics' as subsections under the parent topic. Update content to clarify the distinction between the explain phase and output statistics, and update all cross-references throughout the docs. --- docs/SUMMARY.md | 6 +++--- ...n-matches.md => explainability-and-statistics.md} | 12 +++++++----- docs/interpreting-results/interpret-output-scores.md | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) rename docs/interpreting-results/{explain-matches.md => explainability-and-statistics.md} (52%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index d1b4300ab..613e396cb 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -105,9 +105,9 @@ ## Interpreting Results * [Interpret Output Scores](interpreting-results/interpret-output-scores.md) -* [Explain Matches](interpreting-results/explain-matches.md) -* [Explain a Specific Cluster](interpreting-results/explain-a-specific-cluster.md) -* [Output Statistics](interpreting-results/output-statistics.md) +* [Explainability and Statistics](interpreting-results/explainability-and-statistics.md) + * [Explain a Specific Cluster](interpreting-results/explain-a-specific-cluster.md) + * [Output Statistics](interpreting-results/output-statistics.md) ## Platform Guides diff --git a/docs/interpreting-results/explain-matches.md b/docs/interpreting-results/explainability-and-statistics.md similarity index 52% rename from docs/interpreting-results/explain-matches.md rename to docs/interpreting-results/explainability-and-statistics.md index 698bdf843..ad722048b 100644 --- a/docs/interpreting-results/explain-matches.md +++ b/docs/interpreting-results/explainability-and-statistics.md @@ -8,26 +8,28 @@ tags: primary: true --- -# Explain Matches +# Explainability and Statistics {% hint style="info" icon="right-long" %} Enterprise only. The explain phase is not available in Community. {% endhint %} -Once you have run a match or `runIncremental` phase, you can use the `explainOutput` phase to look under the hood of any cluster. Provide a Zingg ID, and Zingg shows you exactly how that cluster formed, which record pairs were matched probabilistically, their individual scores, and which records did not directly match each other. +Once you have run a match or `runIncremental` phase, you can use the `explain` phase to look under the hood of any cluster. Provide a Zingg ID, and Zingg shows you exactly how that cluster formed, which record pairs were matched probabilistically, and which records did not directly match each other. This is useful for model validation, governance, explaining results to business stakeholders, human-in-the-loop reviews with domain experts, and building confidence in production pipelines. {% hint style="success" icon="right-long" %} -`explainOutput` currently covers probabilistic matches only. Deterministic matches are not included\ +`explain` currently covers probabilistic matches only. Deterministic matches are not included\ in explain output at this time. Clusters formed primarily through deterministic matching rules may return empty results. If you get empty results, verify that the Zingg ID you are querying exists in your match output before running explain. {% endhint %} +Explain answers "why did these specific records end up together?" for one cluster at a time. If you instead want aggregate, run-level visibility — how many clusters formed, how dense they are, how much of the matching came from deterministic rules versus probabilistic scoring, and whether clusters are stable across incremental runs — that's what **output statistics** provides, at three levels: a run-wide **Summary**, per-**Cluster** detail, and per-**Record** detail. + {% hint style="success" icon="right-long" %} **Read more**: * For the step-by-step explain phase task with code - [Explain a specific cluster](explain-a-specific-cluster.md) -* For output statistics - [Output statistics](output-statistics.md) -{% endhint %} +* For record-level and per-cluster match-quality metrics - [Output statistics](output-statistics.md) + {% endhint %} \ No newline at end of file diff --git a/docs/interpreting-results/interpret-output-scores.md b/docs/interpreting-results/interpret-output-scores.md index b5a3c1b39..dfffa1fe9 100644 --- a/docs/interpreting-results/interpret-output-scores.md +++ b/docs/interpreting-results/interpret-output-scores.md @@ -233,6 +233,6 @@ Review clusters with `Z_MINSCORE` of `0` manually to confirm the full cluster is **Read more**: * Z Cluster and Zingg ID - [Z Cluster ID and Zingg ID](../zingg-concepts/z-cluster-and-zingg-id.md) -* Explaining how a cluster formed - [Explain matches](explain-matches.md) +* Explaining how a cluster formed - [Explainability and Statistics](explainability-and-statistics.md) * Improving accuracy when results are wrong - [Improve accuracy](../tuning/improve-accuracy/) {% endhint %} From 6a2743b398cdf953faf04df57b263e1488474aa0 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 14:25:16 +0000 Subject: [PATCH 121/144] GITBOOK-396: No subject --- ...o-solve-and-why-they-are-harder-than-it-looks.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index efc6f25b2..a778e3745 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -6,6 +6,17 @@ description: And why they are harder than it looks Entity resolution at scale is not a data quality problem. It is an algorithmic complexity problem. And it is one that most tools paper over rather than solve. Here is what you are actually dealing with. -
ProblemDescription
The N² comparison problem

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

The variation problem - why rules always breakEntities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance.
The confidence problem - why binary match/no-match is not enough

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

The pairs are not enough problem

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

Managing addition, updation and deletion of records

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

The data drift problem - why models decay

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.

Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

The governance gapExplaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching.
The domain experience gapSome records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)
The schema problem

Since matching is tied heavily to fields, most tools end up imposing restrictions on attributes which can be present in the input. Zingg takes a completely different approach. By learning directly from the data, Zingg works with any fields and their combinations, with no hard requirements for mandatory email, phone number or other fields.

Zingg's approach also powers any entity type like supplier, product etc, not just customer.

+| Problem | Description | +| -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **The N² comparison problem** |

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

| +| **The variation problem - why rules always break** | Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance. | +| **The confidence problem - why binary match/no-match is not enough** |

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

| +| **The pairs are not enough problem** |

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

| +| **Managing addition, updation and deletion of records** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

| +| **The data drift problem - why models decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.

Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

| +| **The governance gap** | Explaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching. | +| **The domain experience gap** |

Some records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)

| +| **The schema problem** |

Since matching is tied heavily to fields, most tools end up imposing restrictions on attributes which can be present in the input. Zingg takes a completely different approach. By learning directly from the data, Zingg works with any fields and their combinations, with no hard requirements for mandatory email, phone number or other fields.

Zingg's approach also powers any entity type like supplier, product etc, not just customer.

| +| **The data privacy problem** | Entity resolution is typically performed on the most sensitive data woned by the Enterprise. Customer lists, Vendor Info, Leads. Sending such data to a third party or a public cloud service |
From 4a4e2f99276e2d6dd170b7aaf66052bba856229e Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 17:24:33 +0000 Subject: [PATCH 122/144] GITBOOK-397: No subject --- ...-built-to-solve-and-why-they-are-harder-than-it-looks.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index a778e3745..e77f4a67a 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -10,13 +10,15 @@ Entity resolution at scale is not a data quality problem. It is an algorithmic c | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **The N² comparison problem** |

Naive entity resolution compares every record against every other record. With N records, that produces N × (N-1) / 2 comparisons.

  • At 10,000 records: ~50 million comparisons.
  • At 1 million records: ~500 billion comparisons.
  • At 10 million records: ~50 trillion comparisons.

No rules engine can evaluate 50 trillion pairs. No team can review them. And no cloud compute budget can sustain running that comparison at production cadence - weekly, daily, or on arrival of new records.This is not a hardware problem. It is a structural one. The comparison space grows quadratically with dataset size, and linear scaling of compute cannot outrun quadratic growth.Zingg's blocking model is the solution. Before any similarity comparison runs, the blocking model groups records into candidate buckets using learned field heuristics. Only records within the same bucket are ever compared. Typical Zingg comparison coverage is 0.05% to 1% of the full problem space - without losing the matching pairs that matter.

| | **The variation problem - why rules always break** | Entities do not appear consistently across systems. "IBM", "I.B.M.", and "International Business Machines" are the same company. "Jon Smith" and "Jonathan Smith" may be the same person. "42 Main St" and "42 Main Street, Apt 3B" are likely the same address.A rule-based approach requires you to enumerate every possible variation for each field. For names alone, the number of possible abbreviations, abbreviation types, and transliteration patterns is unbounded. For addresses, it is worse. For company names across geographies and time periods, it is not practically solvable with rules.Zingg's similarity model handles variation by computing multiple field-level features, character differences, string lengths, common transpositions, prefixes, suffix overlaps, and combining them through a classifier trained on your labeled pairs.The classifier learns the variation patterns in your specific data. It does not need you to enumerate them in advance. | -| **The confidence problem - why binary match/no-match is not enough** |

A rule either fires or it does not. It gives you a binary answer: match or no match.Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

| +| **The confidence problem - why binary match/no-match is not enough** |

A rule either fires or it does not. It gives you a binary answer: match or no match. Production entity resolution requires more than that. Some clusters are high confidence - strong matches across multiple fields that can go straight to automated golden record creation. Others are lower confidence plausible matches that a domain expert should review before merging. Others are borderline pairs that look similar but are probably different entities.Without a graded confidence signal, every match decision has to be treated the same way. You either automate everything and accept errors or review everything manually and abandon the scale benefit entirely. Zingg's similarity model produces a score between 0 and 1 for every candidate pair. That score drives Z_MINSCORE and Z_MAXSCORE on every cluster in your output - so you can route high-confidence clusters to automated processing and low-confidence clusters to a stewardship queue.

Read more:

| | **The pairs are not enough problem** |

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

| | **Managing addition, updation and deletion of records** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

| | **The data drift problem - why models decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.

Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

| | **The governance gap** | Explaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching. | | **The domain experience gap** |

Some records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)

| | **The schema problem** |

Since matching is tied heavily to fields, most tools end up imposing restrictions on attributes which can be present in the input. Zingg takes a completely different approach. By learning directly from the data, Zingg works with any fields and their combinations, with no hard requirements for mandatory email, phone number or other fields.

Zingg's approach also powers any entity type like supplier, product etc, not just customer.

| -| **The data privacy problem** | Entity resolution is typically performed on the most sensitive data woned by the Enterprise. Customer lists, Vendor Info, Leads. Sending such data to a third party or a public cloud service | +| **The data privacy problem** | Entity resolution is typically performed on the most sensitive data woned by the Enterprise. Customer lists, Vendor Info, Leads. Sending such data to a third party or a public cloud service is a major privacy and compliance nightmare. Through warehouse native entity resolution, Zingg ensures your data remains private. | +| **The data pipeline problem** |

Entity Resolution is a fundamental construct in data management and has to align with the enterprise's overall architecture needs.

Zingg's design in all editions ensures entity resolution can run part of your data proessing pipeline, at a cadence of your choice.

Zingg Enterprise provides batch and streaming flows to cater to different architecture choices and design patterns.

By running directly within the data pieline, Zingg maximises data infrastructure ROI since no extra ETL and observability needs to be set for Zingg.

| +| | |
From 2fd57abfc6d6eae85311e676821e04392e09adff Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 17:25:43 +0000 Subject: [PATCH 123/144] GITBOOK-398: No subject --- docs/SUMMARY.md | 2 +- .../zingg-entity-resolution-platform/incremental-run.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 613e396cb..37c5f95ff 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -10,7 +10,7 @@ * [The problems Zingg is built to solve](zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md) * [Deterministic vs Probabilistic Matching](zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) * [Deduplication, linking and resolving](zingg-concepts/zingg-entity-resolution-platform/deduplication-linking-and-resolving.md) - * [Incremental Run](zingg-concepts/zingg-entity-resolution-platform/incremental-run.md) + * [Incremental Flow](zingg-concepts/zingg-entity-resolution-platform/incremental-run.md) * [How Zingg Learns](zingg-concepts/how-zingg-learns/README.md) * [Zingg Active Learning](zingg-concepts/how-zingg-learns/zingg-active-learning.md) * [Zingg Models](zingg-concepts/how-zingg-learns/zingg-models/README.md) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-run.md b/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-run.md index e67d1a46a..16737d245 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-run.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-run.md @@ -1,5 +1,7 @@ --- description: When it is not enough to match once +tags: + - enterprise-only --- # Incremental Flow From 71af1dc9e866929bdc4a027cef9adb66d94f8e48 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 17:26:32 +0000 Subject: [PATCH 124/144] GITBOOK-399: No subject --- .../zingg-entity-resolution-platform/incremental-run.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-run.md b/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-run.md index 16737d245..e5c89971c 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-run.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/incremental-run.md @@ -1,7 +1,8 @@ --- description: When it is not enough to match once tags: - - enterprise-only + - tag: enterprise-only + primary: true --- # Incremental Flow From 124ea0c69563fd5af879f504eaee98eb0c982701 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 17:38:47 +0000 Subject: [PATCH 125/144] GITBOOK-400: No subject --- docs/SUMMARY.md | 2 +- .../platform-guide-for-azure-databricks.md | 10 +++++----- docs/zingg-concepts/zingg-configuration.md | 16 ++++++++++++++++ docs/zingg-concepts/zingg-pipes.md | 11 ----------- 4 files changed, 22 insertions(+), 17 deletions(-) create mode 100644 docs/zingg-concepts/zingg-configuration.md delete mode 100644 docs/zingg-concepts/zingg-pipes.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 37c5f95ff..1e5e26e94 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -35,9 +35,9 @@ * [MAPPING\_(FILENAME) Match](zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md) * [Identity Graph](zingg-concepts/identity-graph.md) * [Z Cluster and Zingg ID](zingg-concepts/z-cluster-and-zingg-id.md) -* [Zingg Pipes](zingg-concepts/zingg-pipes.md) * [Zingg Phases](zingg-concepts/zingg-phases.md) * [Platform Infrastructure vs Data Platform](zingg-concepts/platform-infrastructure-vs-data-platform.md) +* [Zingg Configuration](zingg-concepts/zingg-configuration.md) ## Community And Enterprise Editions diff --git a/docs/platform-guides/platform-guide-for-azure-databricks.md b/docs/platform-guides/platform-guide-for-azure-databricks.md index 9e29f9493..9fd2c941b 100644 --- a/docs/platform-guides/platform-guide-for-azure-databricks.md +++ b/docs/platform-guides/platform-guide-for-azure-databricks.md @@ -27,9 +27,9 @@ Uses `Arguments`, `FieldDefinition`, `CsvPipe`, and `ZinggWithSpark`. The workfl 1. Go to **Compute** → **Create Cluster**. Name it `Zingg-Community`. 2. Set the runtime to a current LTS version for compatibility. 3. Download the latest Zingg JAR from `github.com/zinggAI/zingg/releases`. -4. Open the cluster → **Libraries** → **Install New** → **Upload JAR** → upload the file. +4. Open the cluster → **Libraries** → **Install New** → **Upload JAR** → upload the file. - ![Databricks Install library dialog with the Zingg jar selected from the workspace file path.](../.gitbook/assets/databricks-install-zingg-jar.png) + ![Databricks Install library dialog with the Zingg jar selected from the workspace file path.](../.gitbook/assets/databricks-install-zingg-jar.png) #### **Step 2: Install the Zingg Python package** @@ -289,7 +289,7 @@ display(df) print(df.count()) ``` -![Databricks match output table with Z_MINSCORE, Z_MAXSCORE and Z_CLUSTER columns; several rows share Z_CLUSTER 3, meaning they resolved to the same entity.](../.gitbook/assets/databricks-match-output.png) +![Databricks match output table with Z\_MINSCORE, Z\_MAXSCORE and Z\_CLUSTER columns; several rows share Z\_CLUSTER 3, meaning they resolved to the same entity.](../.gitbook/assets/databricks-match-output.png) {% hint style="success" icon="right-long" %} Records sharing the same `Z_CLUSTER` value have been resolved to the same real-world entity. `Z_MINSCORE` is the weakest match confidence within the cluster. `Z_MAXSCORE` is the strongest. For full output column definitions → [Interpret Output Scores](../interpreting-results/interpret-output-scores.md). @@ -308,7 +308,7 @@ DOCS_DIR = zinggDir + "/" + modelId + "/docs/" displayHTML(open(DOCS_DIR + "model.html", 'r').read()) ``` -_**IMAGE TO BE ADDED —**** ****`generateDocs`**** ****HTML output rendered inside a Databricks notebook showing labeled pair examples. Tanwi to check with team for screenshot from a live notebook run. Even a small portion of the rendered HTML is sufficient — it tells the reader what to expect before they run it. Place: below the**** ****`displayHTML`****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*line.**_ +_**IMAGE TO BE ADDED —**** ****`generateDocs`**** ****HTML output rendered inside a Databricks notebook showing labeled pair examples. Tanwi to check with team for screenshot from a live notebook run. Even a small portion of the rendered HTML is sufficient — it tells the reader what to expect before they run it. Place: below the**** ****`displayHTML`****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*line.**_ {% endtab %} {% tab title="Enterprise" %} @@ -630,7 +630,7 @@ data_html = "\n".join(r.value for r in data_doc.collect()) displayHTML(data_html) ``` -_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*output rendered inside a Databricks notebook showing labeled pair examples in HTML. Tanwi to check with team for screenshot from a live notebook run. Can reuse the OS version if the output looks the same.**_ +_**IMAGE TO BE ADDED —**** ****`generateDocs`****\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \*\*\*\*output rendered inside a Databricks notebook showing labeled pair examples in HTML. Tanwi to check with team for screenshot from a live notebook run. Can reuse the OS version if the output looks the same.**_ ### Notebook 05: Train and match diff --git a/docs/zingg-concepts/zingg-configuration.md b/docs/zingg-concepts/zingg-configuration.md new file mode 100644 index 000000000..bf531e763 --- /dev/null +++ b/docs/zingg-concepts/zingg-configuration.md @@ -0,0 +1,16 @@ +# Zingg Configuration + +To resolve entities, Zingg needs some user inputs or arguments. These arguments help Zingg understand where the input data is, which fields to use in the matching and how, where the output is to be written, where the models are to be persisted or used from. The user can also provide some performance specific settings. + +The configuration can be done either through JSON, or through Zingg's Python API. + +Here is some important terminology to be aware of: + + + +1. Arguments - representative of the user input comprising of input and output data, fields and their matching criteria, models location as well as performance criteria. Zingg Enterprise also has specialised arguments for functionality like incremental run, reassign and diff etc. +2. Pipes - Zingg's abstraction for the data store. A pipe encapsulates the source or destination of records; a delta file path, a Snowflake table, a UC table, an RDBMS dataset. Different pipes are configured by passing the format string in the configuration. + +{% hint style="success" icon="right-long" %} +**Read more**: [Pipes and Data Connections](../connect-your-data/pipes-and-data-connections.md) +{% endhint %} diff --git a/docs/zingg-concepts/zingg-pipes.md b/docs/zingg-concepts/zingg-pipes.md deleted file mode 100644 index 3ac6d33c0..000000000 --- a/docs/zingg-concepts/zingg-pipes.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -description: Connecting to data ---- - -# Zingg Pipes - -Pipes are Zingg's abstraction for the data store. A pipe encapsulates the source or destination of records; a delta file path, a Snowflake table, a UC table, an RDBMS dataset. Different pipes are configured by passing the format string in the configuration. - -{% hint style="success" icon="right-long" %} -**Read more**: [Pipes and Data Connections](../connect-your-data/pipes-and-data-connections.md) -{% endhint %} From cca0321817fc6564ac2e6b6b58874ad718bf518d Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 17:43:48 +0000 Subject: [PATCH 126/144] GITBOOK-401: No subject --- docs/SUMMARY.md | 2 +- ...ating-from-community-to-enterprise-content-to-be-added.md | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 1e5e26e94..2210a4379 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -42,7 +42,7 @@ ## Community And Enterprise Editions * [Community vs Enterprise](community-and-enterprise-editions/community-vs-enterprise/README.md) - * [Migrating from Community to Enterprise (Content to be Added)](community-and-enterprise-editions/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) + * [Migrating from Community to Enterprise](community-and-enterprise-editions/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md) ## Running Zingg diff --git a/docs/community-and-enterprise-editions/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md b/docs/community-and-enterprise-editions/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md index 189760471..cc6e5c076 100644 --- a/docs/community-and-enterprise-editions/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md +++ b/docs/community-and-enterprise-editions/community-vs-enterprise/migrating-from-community-to-enterprise-content-to-be-added.md @@ -4,5 +4,8 @@ description: >- names, and new features unlocked. --- -# Migrating from Community to Enterprise (Content to be Added) +# Migrating from Community to Enterprise +Zingg Enterprise is a near drop in replacement to Zingg Community. The same models can be migrated from the Community to the Enterprise if you prefer, or you can tune them, or build newer faster and more accurate ones. Zingg Enterprise on Snowflake even comes with utilities to copy over models to Snowflake Tables. \ +\ +The configuration JSON and Python API are designed to be similar to reduce migration effort. Most of the time in the migration would be in using the new set of features available in Zingg Enterprise. From 101531524abcd2ccbf802d8172da46b14db842b3 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 19:19:20 +0000 Subject: [PATCH 127/144] GITBOOK-402: No subject --- docs/SUMMARY.md | 38 +++++++++---------- .../concept-glossary.md | 2 +- .../platform-guide-for-aws-glue.md | 2 +- .../platform-guide-for-azure-databricks.md | 2 +- .../platform-guide-for-gcp-dataproc.md | 2 +- .../platform-guide-for-microsoft-fabric.md | 37 ++++++++---------- docs/reference/configuration-schema.md | 4 +- docs/running-zingg/configure-zingg.md | 3 +- docs/running-zingg/quick-start-docker.md | 2 +- docs/running-zingg/step-by-step-guide.md | 12 +++--- .../security-and-privacy.md | 6 +-- docs/standardize-fields-and-results.md | 2 +- .../tuning/configure-field-standardization.md | 2 +- docs/tuning/improve-accuracy/README.md | 2 +- .../zingg-concepts/how-zingg-learns/README.md | 2 +- .../zingg-models/blocking-model.md | 6 +-- .../README.md} | 6 +-- .../field-definition/README.md} | 2 +- .../field-definition}/match-types/README.md | 0 .../match-types/dont_use-match.md | 0 .../match-types/email-match.md | 0 .../match-types/email_optimised-match.md | 0 .../match-types/exact-match.md | 2 +- .../match-types/fuzzy-match.md | 2 +- .../match-types/fuzzy_optimised-match.md | 0 .../match-types/mapping_-filename-match.md | 0 .../match-types/null_or_blank-match.md | 0 .../match-types/numeric-match.md | 0 .../match-types/numeric_with_units-match.md | 0 .../match-types/only_alphabets_exact-match.md | 0 .../match-types/only_alphabets_fuzzy-match.md | 0 .../only_alphabets_fuzzy_optimised-match.md | 0 .../match-types/pincode-match.md | 0 .../match-types/text-match.md | 0 docs/zingg-python-api/community-python-api.md | 2 +- .../enterprise-zinggec-python-api.md | 2 +- 36 files changed, 66 insertions(+), 74 deletions(-) rename docs/zingg-concepts/{zingg-configuration.md => zingg-configuration/README.md} (88%) rename docs/zingg-concepts/{how-zingg-learns/field-definition.md => zingg-configuration/field-definition/README.md} (85%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/README.md (100%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/dont_use-match.md (100%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/email-match.md (100%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/email_optimised-match.md (100%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/exact-match.md (97%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/fuzzy-match.md (97%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/fuzzy_optimised-match.md (100%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/mapping_-filename-match.md (100%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/null_or_blank-match.md (100%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/numeric-match.md (100%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/numeric_with_units-match.md (100%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/only_alphabets_exact-match.md (100%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/only_alphabets_fuzzy-match.md (100%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/only_alphabets_fuzzy_optimised-match.md (100%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/pincode-match.md (100%) rename docs/zingg-concepts/{how-zingg-learns => zingg-configuration/field-definition}/match-types/text-match.md (100%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 2210a4379..597f6e76b 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -16,28 +16,28 @@ * [Zingg Models](zingg-concepts/how-zingg-learns/zingg-models/README.md) * [Blocking Model](zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) * [Similarity Model](zingg-concepts/how-zingg-learns/zingg-models/similarity-model.md) - * [Field Definition](zingg-concepts/how-zingg-learns/field-definition.md) - * [Match Type](zingg-concepts/how-zingg-learns/match-types/README.md) - * [FUZZY Match](zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md) - * [FUZZY\_OPTIMISED Match](zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md) - * [EXACT Match](zingg-concepts/how-zingg-learns/match-types/exact-match.md) - * [EMAIL Match](zingg-concepts/how-zingg-learns/match-types/email-match.md) - * [EMAIL\_OPTIMISED Match](zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md) - * [PINCODE Match](zingg-concepts/how-zingg-learns/match-types/pincode-match.md) - * [NUMERIC Match](zingg-concepts/how-zingg-learns/match-types/numeric-match.md) - * [NUMERIC\_WITH\_UNITS Match](zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md) - * [TEXT Match](zingg-concepts/how-zingg-learns/match-types/text-match.md) - * [ONLY\_ALPHABETS\_EXACT Match](zingg-concepts/how-zingg-learns/match-types/only_alphabets_exact-match.md) - * [ONLY\_ALPHABETS\_FUZZY Match](zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy-match.md) - * [ONLY\_ALPHABETS\_FUZZY\_OPTIMISED Match](zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md) - * [NULL\_OR\_BLANK Match](zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md) - * [DONT\_USE Match](zingg-concepts/how-zingg-learns/match-types/dont_use-match.md) - * [MAPPING\_(FILENAME) Match](zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md) * [Identity Graph](zingg-concepts/identity-graph.md) * [Z Cluster and Zingg ID](zingg-concepts/z-cluster-and-zingg-id.md) * [Zingg Phases](zingg-concepts/zingg-phases.md) * [Platform Infrastructure vs Data Platform](zingg-concepts/platform-infrastructure-vs-data-platform.md) -* [Zingg Configuration](zingg-concepts/zingg-configuration.md) +* [Zingg Configuration](zingg-concepts/zingg-configuration/README.md) + * [Field Definition](zingg-concepts/zingg-configuration/field-definition/README.md) + * [Match Type](zingg-concepts/zingg-configuration/field-definition/match-types/README.md) + * [FUZZY Match](zingg-concepts/zingg-configuration/field-definition/match-types/fuzzy-match.md) + * [FUZZY\_OPTIMISED Match](zingg-concepts/zingg-configuration/field-definition/match-types/fuzzy_optimised-match.md) + * [EXACT Match](zingg-concepts/zingg-configuration/field-definition/match-types/exact-match.md) + * [EMAIL Match](zingg-concepts/zingg-configuration/field-definition/match-types/email-match.md) + * [EMAIL\_OPTIMISED Match](zingg-concepts/zingg-configuration/field-definition/match-types/email_optimised-match.md) + * [PINCODE Match](zingg-concepts/zingg-configuration/field-definition/match-types/pincode-match.md) + * [NUMERIC Match](zingg-concepts/zingg-configuration/field-definition/match-types/numeric-match.md) + * [NUMERIC\_WITH\_UNITS Match](zingg-concepts/zingg-configuration/field-definition/match-types/numeric_with_units-match.md) + * [TEXT Match](zingg-concepts/zingg-configuration/field-definition/match-types/text-match.md) + * [ONLY\_ALPHABETS\_EXACT Match](zingg-concepts/zingg-configuration/field-definition/match-types/only_alphabets_exact-match.md) + * [ONLY\_ALPHABETS\_FUZZY Match](zingg-concepts/zingg-configuration/field-definition/match-types/only_alphabets_fuzzy-match.md) + * [ONLY\_ALPHABETS\_FUZZY\_OPTIMISED Match](zingg-concepts/zingg-configuration/field-definition/match-types/only_alphabets_fuzzy_optimised-match.md) + * [NULL\_OR\_BLANK Match](zingg-concepts/zingg-configuration/field-definition/match-types/null_or_blank-match.md) + * [DONT\_USE Match](zingg-concepts/zingg-configuration/field-definition/match-types/dont_use-match.md) + * [MAPPING\_(FILENAME) Match](zingg-concepts/zingg-configuration/field-definition/match-types/mapping_-filename-match.md) ## Community And Enterprise Editions @@ -46,7 +46,7 @@ ## Running Zingg -* [Step-by-Step Guide](running-zingg/step-by-step-guide.md) +* [Step-by-Step Overview](running-zingg/step-by-step-guide.md) * [Experience Zingg](running-zingg/experience-zingg.md) * [Quick Start (Docker)](running-zingg/quick-start-docker.md) * [Install Zingg](running-zingg/install-zingg.md) diff --git a/docs/frequently-asked-questions/concept-glossary.md b/docs/frequently-asked-questions/concept-glossary.md index f754d8b15..ee8bff033 100644 --- a/docs/frequently-asked-questions/concept-glossary.md +++ b/docs/frequently-asked-questions/concept-glossary.md @@ -96,7 +96,7 @@ Learn how field definitions, match types, and related settings influence matchin Match types are the combination of similarity functions assigned to each field, telling Zingg how to compare values in that field across records. Zingg provides match types for fuzzy comparison, exact match, email, numeric, text, and several specialized types for alphabet-only or null handling. -Each field receives one match type. The full list of all match types; what they do, when to use each, and examples on [Match Types](../zingg-concepts/how-zingg-learns/match-types/). +Each field receives one match type. The full list of all match types; what they do, when to use each, and examples on [Match Types](../zingg-concepts/zingg-configuration/field-definition/match-types/). A quick reference for the most common fields: diff --git a/docs/platform-guides/platform-guide-for-aws-glue.md b/docs/platform-guides/platform-guide-for-aws-glue.md index c9f1ed441..98d1b884e 100644 --- a/docs/platform-guides/platform-guide-for-aws-glue.md +++ b/docs/platform-guides/platform-guide-for-aws-glue.md @@ -356,7 +356,7 @@ args.setFieldDefinition(fieldDefs) {% hint style="success" icon="right-long" %} `FUZZY` handles variations like 'Jon' vs 'John' or 'St' vs 'Street'. `EXACT` requires a character-for-character match. `DONT_USE` excludes a field from matching but keeps it in output. -**Read more**: For all match types → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +**Read more**: For all match types → [Match Types](../zingg-concepts/zingg-configuration/field-definition/match-types/) {% endhint %} #### **Step 13: Configure performance settings** diff --git a/docs/platform-guides/platform-guide-for-azure-databricks.md b/docs/platform-guides/platform-guide-for-azure-databricks.md index 9fd2c941b..c1fd14d2b 100644 --- a/docs/platform-guides/platform-guide-for-azure-databricks.md +++ b/docs/platform-guides/platform-guide-for-azure-databricks.md @@ -184,7 +184,7 @@ args.setFieldDefinition([ ``` {% hint style="success" icon="right-long" %} -`FUZZY` handles variations like 'Jon' vs 'John' or 'St' vs 'Street'. `EXACT` requires a character-for-character match. `DONT_USE` excludes a field from matching but keeps it in the output - use this for identifiers like record IDs. For all match types → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +`FUZZY` handles variations like 'Jon' vs 'John' or 'St' vs 'Street'. `EXACT` requires a character-for-character match. `DONT_USE` excludes a field from matching but keeps it in the output - use this for identifiers like record IDs. For all match types → [Match Types](../zingg-concepts/zingg-configuration/field-definition/match-types/) {% endhint %} ### Notebook 02: Find training data and label pairs diff --git a/docs/platform-guides/platform-guide-for-gcp-dataproc.md b/docs/platform-guides/platform-guide-for-gcp-dataproc.md index b1c2961b4..56703817d 100644 --- a/docs/platform-guides/platform-guide-for-gcp-dataproc.md +++ b/docs/platform-guides/platform-guide-for-gcp-dataproc.md @@ -258,7 +258,7 @@ args.setFieldDefinition(fieldDefs) {% hint style="success" icon="right-long" %} `FUZZY` handles variations like 'Jon' vs 'John' or 'St' vs 'Street'. `EXACT` requires a character-for-character match. `DONT_USE` excludes a field from matching but keeps it in output. -For all match types → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +For all match types → [Match Types](../zingg-concepts/zingg-configuration/field-definition/match-types/) {% endhint %} ### Step 10: Configure performance settings diff --git a/docs/platform-guides/platform-guide-for-microsoft-fabric.md b/docs/platform-guides/platform-guide-for-microsoft-fabric.md index a143939c6..251ebd19c 100644 --- a/docs/platform-guides/platform-guide-for-microsoft-fabric.md +++ b/docs/platform-guides/platform-guide-for-microsoft-fabric.md @@ -28,49 +28,45 @@ If you are new to Fabric, sign up for a free trial at `microsoft.com/fabric`. 1. Go to **Workspaces** and click **New workspace**. 2. Name it something like `Zingg-Fabric`. -3. When prompted for a session cluster, choose **New Standard Session**. +3. When prompted for a session cluster, choose **New Standard Session**. - ![Fabric session dropdown with New standard session selected.](../.gitbook/assets/fabric-new-standard-session.png) + ![Fabric session dropdown with New standard session selected.](../.gitbook/assets/fabric-new-standard-session.png) #### **Step 2: Create a Zingg Environment** Fabric Environments let you install JARs that persist across notebook sessions. Zingg requires its JAR to be installed in an Environment before any notebook runs. 1. Inside your workspace, go to the **Environment** tab and click **New Environment**. -2. Name it `Zingg Environment`. +2. Name it `Zingg Environment`. - ![Fabric notebook toolbar with the environment dropdown open on New environment.](../.gitbook/assets/fabric-new-environment.png) + ![Fabric notebook toolbar with the environment dropdown open on New environment.](../.gitbook/assets/fabric-new-environment.png) #### **Step 3: Install the Zingg JAR in the Environment** The Zingg JAR must be installed as a Custom Library in your Environment so Fabric's Spark runtime can find it. -1. Go to `github.com/zinggAI/zingg/releases` and download the latest release `tar` file. +1. Go to `github.com/zinggAI/zingg/releases` and download the latest release `tar` file. - ![Zingg GitHub release page showing the spark tar.gz file under Assets.](../.gitbook/assets/fabric-zingg-release-assets.png) + ![Zingg GitHub release page showing the spark tar.gz file under Assets.](../.gitbook/assets/fabric-zingg-release-assets.png) +2. Extract the `tar` file and locate the JAR file inside it. -2. Extract the `tar` file and locate the JAR file inside it. - - ![Extracted Zingg release folder with the zingg jar file highlighted.](../.gitbook/assets/fabric-zingg-jar-extracted.png) - -3. Open your `Zingg Environment`, go to **Custom Library**, and upload the JAR file. - - ![Fabric Environment Custom libraries page showing the uploaded Zingg jar with Success status.](../.gitbook/assets/fabric-custom-library-upload.png) + ![Extracted Zingg release folder with the zingg jar file highlighted.](../.gitbook/assets/fabric-zingg-jar-extracted.png) +3. Open your `Zingg Environment`, go to **Custom Library**, and upload the JAR file. + ![Fabric Environment Custom libraries page showing the uploaded Zingg jar with Success status.](../.gitbook/assets/fabric-custom-library-upload.png) 4. Click **Save** and then **Publish** the Environment. #### **Step 4: Create a Lakehouse and upload your data** Zingg reads from and writes to OneLake. Create a Lakehouse to give Zingg a storage location for your data, model files, and output. -1. Inside your workspace, click **New Item** → **Lakehouse**. - - ![Fabric New item panel with Lakehouse selected under Store data.](../.gitbook/assets/fabric-new-item-lakehouse.png) +1. Inside your workspace, click **New Item** → **Lakehouse**. + ![Fabric New item panel with Lakehouse selected under Store data.](../.gitbook/assets/fabric-new-item-lakehouse.png) 2. Give the Lakehouse a name (for example `ZinggLakehouse`). -3. Go inside the Lakehouse, click **Get Data**, and upload your CSV file. +3. Go inside the Lakehouse, click **Get Data**, and upload your CSV file. - ![Lakehouse Get data menu with Upload files selected.](../.gitbook/assets/fabric-lakehouse-upload-files.png) + ![Lakehouse Get data menu with Upload files selected.](../.gitbook/assets/fabric-lakehouse-upload-files.png) {% hint style="success" icon="right-long" %} Sample data for testing: `github.com/zinggAI/zingg/blob/main/examples/febrl/test.csv` @@ -279,7 +275,7 @@ args.setFieldDefinition(fieldDefs) ``` {% hint style="success" icon="right-long" %} -`FUZZY` handles variations like 'Jon' vs 'John' or 'St' vs 'Street'. `EXACT` requires a character-for-character match. `DONT_USE` excludes a field from matching but keeps it in output — use this for record identifiers. For all match types → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +`FUZZY` handles variations like 'Jon' vs 'John' or 'St' vs 'Street'. `EXACT` requires a character-for-character match. `DONT_USE` excludes a field from matching but keeps it in output — use this for record identifiers. For all match types → [Match Types](../zingg-concepts/zingg-configuration/field-definition/match-types/) {% endhint %} ### Notebook 02: Find training data and label pairs @@ -480,8 +476,7 @@ display(outputDF) print(outputDF.count()) ``` -![Zingg match output table in a Fabric notebook with z_minScore, z_maxScore and z_cluster columns alongside the record fields.](../.gitbook/assets/fabric-match-output.png) - +![Zingg match output table in a Fabric notebook with z\_minScore, z\_maxScore and z\_cluster columns alongside the record fields.](../.gitbook/assets/fabric-match-output.png) {% endtab %} {% tab title="Enterprise" %} diff --git a/docs/reference/configuration-schema.md b/docs/reference/configuration-schema.md index 9a87b4d17..b7014ab1d 100644 --- a/docs/reference/configuration-schema.md +++ b/docs/reference/configuration-schema.md @@ -20,7 +20,7 @@ This page is the reference. Every Zingg configuration parameter, JSON key and Py Each entry in the `fieldDefinition` array defines one field in your input schema. -
ParameterTypeEditionDescription
fieldNamestringAll editionsName of the field in your input dataset. Must match exactly.
matchTypeenumAll editionsHow to compare this field. See Match Types for all values.
fieldsstringAll editionsThe field to use for comparison. Keep the same as fieldName for standard use. Can reference a different column name if the field you want to compare appears under a different name in the input schema. For now, keep this the same as fieldName unless specifically advised otherwise.
dataTypestringAll editionsSpark SQL data type - string, integer, double, date, timestamp.
stopWordsstring (path)All editions, optionalPath to a CSV file of stopwords for this field. One word per row. The file must have a header row. Generated by the recommend phase.
primaryKeybooleanEnterprise only, optionalMarks this field as the primary key. Used by runIncremental to identify records uniquely across runs and by reassignZinggId for ID preservation. Set true on exactly one field per config.
postProcessorsstringEnterprise only, optionalStandardise postprocessor reference. Format: STANDARDISE_<basename> where <basename>.json is the mapping file. Normalises field values to canonical form after matching.
+
ParameterTypeEditionDescription
fieldNamestringAll editionsName of the field in your input dataset. Must match exactly.
matchTypeenumAll editionsHow to compare this field. See Match Types for all values.
fieldsstringAll editionsThe field to use for comparison. Keep the same as fieldName for standard use. Can reference a different column name if the field you want to compare appears under a different name in the input schema. For now, keep this the same as fieldName unless specifically advised otherwise.
dataTypestringAll editionsSpark SQL data type - string, integer, double, date, timestamp.
stopWordsstring (path)All editions, optionalPath to a CSV file of stopwords for this field. One word per row. The file must have a header row. Generated by the recommend phase.
primaryKeybooleanEnterprise only, optionalMarks this field as the primary key. Used by runIncremental to identify records uniquely across runs and by reassignZinggId for ID preservation. Set true on exactly one field per config.
postProcessorsstringEnterprise only, optionalStandardise postprocessor reference. Format: STANDARDISE_<basename> where <basename>.json is the mapping file. Normalises field values to canonical form after matching.
### `data/output` (pipe) @@ -88,7 +88,7 @@ Sample config files: **Related pages:** * [Configure Zingg](../running-zingg/configure-zingg.md) - step-by-step task of building your config -* [Match Types](../zingg-concepts/how-zingg-learns/match-types/) - full reference for all `matchType` values +* [Match Types](../zingg-concepts/zingg-configuration/field-definition/match-types/) - full reference for all `matchType` values * [Connect Your Data](../connect-your-data/pipes-and-data-connections.md) - all `data` and `output` pipe formats and props * [Working With Python](../zingg-python-api/working-with-python.md) - Python API alternative to JSON config {% endhint %} diff --git a/docs/running-zingg/configure-zingg.md b/docs/running-zingg/configure-zingg.md index a92b0afff..027d767f6 100644 --- a/docs/running-zingg/configure-zingg.md +++ b/docs/running-zingg/configure-zingg.md @@ -173,7 +173,7 @@ args.setFieldDefinition(fieldDefs) ``` {% hint style="success" icon="right-long" %} -**Read more**: Match types reference - [Match types](../zingg-concepts/how-zingg-learns/match-types/) | [Configuration schema](../reference/configuration-schema.md) +**Read more**: Match types reference - [Match types](../zingg-concepts/zingg-configuration/field-definition/match-types/) | [Configuration schema](../reference/configuration-schema.md) {% endhint %} ### Step 4: Configure input and output pipes @@ -554,7 +554,6 @@ args.setPassthroughExpr("fname = 'matilda'") {% endtab %} {% tab title="Enterprise Snowflake" %} - ### Step 1: Build the Enterprise arguments object #### JSON diff --git a/docs/running-zingg/quick-start-docker.md b/docs/running-zingg/quick-start-docker.md index 5acacc194..6defe45d1 100644 --- a/docs/running-zingg/quick-start-docker.md +++ b/docs/running-zingg/quick-start-docker.md @@ -71,7 +71,7 @@ Replace `` with your running container's ID (find it with `docker Create or edit your configuration. The config defines your field definitions, input and output paths, model ID, and partition settings. Both editions use the same JSON structure — only the Python class differs between editions. {% hint style="success" icon="right-long" %} -`FUZZY` handles typos and abbreviations. `EXACT` requires character-for-character match. `DONT_USE` excludes a field from matching but keeps it in output. For all match types → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +`FUZZY` handles typos and abbreviations. `EXACT` requires character-for-character match. `DONT_USE` excludes a field from matching but keeps it in output. For all match types → [Match Types](../zingg-concepts/zingg-configuration/field-definition/match-types/) {% endhint %} #### Python - Community diff --git a/docs/running-zingg/step-by-step-guide.md b/docs/running-zingg/step-by-step-guide.md index 962d42ea6..080f49707 100644 --- a/docs/running-zingg/step-by-step-guide.md +++ b/docs/running-zingg/step-by-step-guide.md @@ -4,11 +4,11 @@ description: >- for the entire Zingg docs site. --- -# Step-by-Step Guide +# Step-by-Step Overview Zingg works in phases. Each phase performs a specific task and produces an output that the next phase depends on. You do not need to run all phases every time. Once the model is trained, you run `match`, `link`, or `runIncremental` directly. -The phases below represent the complete workflow from first setup to production matching. This page describes the workflow you follow once Zingg is installed. +The steps below represent the complete workflow from first setup to production matching. This page describes the workflow you follow once Zingg is installed. {% hint style="success" icon="right-long" %} This page describes the workflow you follow once Zingg is installed.\ @@ -29,9 +29,9 @@ Configure input and output pipes to tell Zingg where your source data lives and {% endstep %} {% step %} -### Configure Zingg +### Configure fields -Define your field definitions, match types, model ID, partition count, and any optional Enterprise features (deterministic matching, primary key, pass through, standardization). The configuration drives every downstream phase. +Define your field definitions, match types, model ID, partition count, and relevant Enterprise features (deterministic matching, primary key, pass through, standardization). The configuration drives every downstream phase. {% hint style="success" icon="right-long" %} **Read more**: [Content your Data](../connect-your-data/pipes-and-data-connections.md) @@ -54,7 +54,7 @@ Run `findTrainingData` to generate candidate pairs, then label each pair as Matc {% step %} ### Verify Blocking -Run `verifyBlocking` to check what percentage of your known matching pairs are being blocked together correctly. Run after labelling and before committing to a full training run. If coverage is low, return to labelling. +Run `verifyBlocking` to check what percentage of your known matching pairs are being blocked together correctly. Run after labelling and before committing to a full training run. If coverage is low, return to labelling. Enterprise Only. {% hint style="success" icon="right-long" %} **Read more**: [Verify Blocking](verify-blocking.md) @@ -82,7 +82,7 @@ Run `train` to build and save the blocking and similarity models from your label {% endstep %} {% step %} -### Run the Match Phase or Link across Datasets +### Match or Link across Datasets Apply the trained model to your data: diff --git a/docs/security-and-privacy/security-and-privacy.md b/docs/security-and-privacy/security-and-privacy.md index 9167484c9..10e061b47 100644 --- a/docs/security-and-privacy/security-and-privacy.md +++ b/docs/security-and-privacy/security-and-privacy.md @@ -32,7 +32,7 @@ handled carefully: * Consider pseudonymizing or tokenizing highly sensitive fields before running Zingg if your data governance policy requires it. Zingg can match on tokens as effectively as on raw values when the tokenization is consistent. {% hint style="success" icon="right-long" %} -**Read more**: For the full match type reference → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +**Read more**: For the full match type reference → [Match Types](../zingg-concepts/zingg-configuration/field-definition/match-types/) {% endhint %} ### GDPR and CCPA considerations @@ -46,6 +46,6 @@ Zingg is a processing tool. GDPR and CCPA compliance obligations apply to how yo {% hint style="success" icon="right-long" %} **Read more**: For GDPR and CCPA identity resolution use cases: -* [GDPR use case on identity resolution](https://zingg.ai/product/entity-resolution-solutions/gdpr) -* [CCPA use case on identity resolution](https://zingg.ai/product/entity-resolution-solutions/ccpa) +* [GDPR use case on identity resolution](https://zingg.ai/product/entity-resolution-solutions/gdpr) +* [CCPA use case on identity resolution](https://zingg.ai/product/entity-resolution-solutions/ccpa) {% endhint %} diff --git a/docs/standardize-fields-and-results.md b/docs/standardize-fields-and-results.md index 1b1f40012..537175cf6 100644 --- a/docs/standardize-fields-and-results.md +++ b/docs/standardize-fields-and-results.md @@ -62,5 +62,5 @@ Enforce canonical values on classification fields like industry codes, status la * To create a mapping file and configure the postprocessor on a field → [Configure Field Standardization](tuning/configure-field-standardization.md) * For the full field definition configuration including all `EFieldDefinition` methods - [Configure Zingg](running-zingg/configure-zingg.md) -* For dictionary-based matching using the MAPPING match type (nicknames, aliases, company names) - [Match Types](zingg-concepts/how-zingg-learns/match-types/) +* For dictionary-based matching using the MAPPING match type (nicknames, aliases, company names) - [Match Types](zingg-concepts/zingg-configuration/field-definition/match-types/) {% endhint %} diff --git a/docs/tuning/configure-field-standardization.md b/docs/tuning/configure-field-standardization.md index 770010e12..58791d2bd 100644 --- a/docs/tuning/configure-field-standardization.md +++ b/docs/tuning/configure-field-standardization.md @@ -131,5 +131,5 @@ If the output still shows raw variants: * [Standardize Fields and Results](../standardize-fields-and-results.md) - concept and where it is useful * [Configure Zingg](../running-zingg/configure-zingg.md) - full field definition configuration including all `EFieldDefinition` methods -* [Match Types](../zingg-concepts/how-zingg-learns/match-types/) - for the MAPPING match type, which uses a similar mapping file approach for matching +* [Match Types](../zingg-concepts/zingg-configuration/field-definition/match-types/) - for the MAPPING match type, which uses a similar mapping file approach for matching {% endhint %} diff --git a/docs/tuning/improve-accuracy/README.md b/docs/tuning/improve-accuracy/README.md index f68fcb036..77b3f4fa0 100644 --- a/docs/tuning/improve-accuracy/README.md +++ b/docs/tuning/improve-accuracy/README.md @@ -34,7 +34,7 @@ Review your `FieldDefinition` match types. Common mismatches: * Using `DONT_USE` for a field that actually carries strong identity signal - adding it back with the right match type can improve recall {% hint style="success" icon="right-long" %} -**Read more**: Match types reference - [Match types](../../zingg-concepts/how-zingg-learns/match-types/) +**Read more**: Match types reference - [Match types](../../zingg-concepts/zingg-configuration/field-definition/match-types/) {% endhint %} ### Step 3: Remove stopwords diff --git a/docs/zingg-concepts/how-zingg-learns/README.md b/docs/zingg-concepts/how-zingg-learns/README.md index fdb6d06dd..dba18c3bb 100644 --- a/docs/zingg-concepts/how-zingg-learns/README.md +++ b/docs/zingg-concepts/how-zingg-learns/README.md @@ -32,5 +32,5 @@ When you define your field configuration, you choose a match type for each field * For full field definition setup including the `fields`, `dataType`, and `stopWords` attributes -\ [Configure Zingg](../../running-zingg/configure-zingg.md) -* For the complete match types reference with all 12 types - [Match Types](match-types/) +* For the complete match types reference with all 12 types - [Match Types](../zingg-configuration/field-definition/match-types/) {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md index f4f396268..5b7b8f073 100644 --- a/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md +++ b/docs/zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md @@ -15,7 +15,7 @@ consequential filter in the pipeline. ### Blocking functions -The blocking model is a purpose built entity resolution model, comprising of a tree with hash functions. Blocking functions control which records are compared. The blocking tree learns on the matched records you provided during labeling; at every node, Zingg selects the function and the field that produces the least elimination of your known matching pairs. +The blocking model is a purpose built entity resolution micro clustering model, comprising of a tree with hash functions. Blocking functions control which records are compared. The blocking tree learns on the matched records you provided during labeling; at every node, Zingg selects the function and the field that produces the least elimination of your known matching pairs. **What makes a good blocking function:** @@ -45,9 +45,7 @@ Pair 1 is eliminated (`n` ≠ `h`). `last1char` is not a good function for `firs So `first1char(firstname)` will be selected. It brings near-similar records together - clustering them to break the cartesian join. -The good part is that the user does not have to think about these constructs at all. During active learning, the model is automatically learnt based on what the user labels. - - +The good part is that the user does not have to think about these constructs at all. During active learning, the model is automatically learnt based on what the user labels. {% hint style="success" icon="right-long" %} The blocking model learns from your labeled training pairs, the same training data that teaches the\ diff --git a/docs/zingg-concepts/zingg-configuration.md b/docs/zingg-concepts/zingg-configuration/README.md similarity index 88% rename from docs/zingg-concepts/zingg-configuration.md rename to docs/zingg-concepts/zingg-configuration/README.md index bf531e763..e0bf06d68 100644 --- a/docs/zingg-concepts/zingg-configuration.md +++ b/docs/zingg-concepts/zingg-configuration/README.md @@ -6,11 +6,11 @@ The configuration can be done either through JSON, or through Zingg's Python API Here is some important terminology to be aware of: - - 1. Arguments - representative of the user input comprising of input and output data, fields and their matching criteria, models location as well as performance criteria. Zingg Enterprise also has specialised arguments for functionality like incremental run, reassign and diff etc. 2. Pipes - Zingg's abstraction for the data store. A pipe encapsulates the source or destination of records; a delta file path, a Snowflake table, a UC table, an RDBMS dataset. Different pipes are configured by passing the format string in the configuration. +3. Field Definition - +4. Match type - {% hint style="success" icon="right-long" %} -**Read more**: [Pipes and Data Connections](../connect-your-data/pipes-and-data-connections.md) +**Read more**: [Pipes and Data Connections](../../connect-your-data/pipes-and-data-connections.md) {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/field-definition.md b/docs/zingg-concepts/zingg-configuration/field-definition/README.md similarity index 85% rename from docs/zingg-concepts/how-zingg-learns/field-definition.md rename to docs/zingg-concepts/zingg-configuration/field-definition/README.md index 9f2d71b26..5a26a1544 100644 --- a/docs/zingg-concepts/how-zingg-learns/field-definition.md +++ b/docs/zingg-concepts/zingg-configuration/field-definition/README.md @@ -9,5 +9,5 @@ Field Definition is the configuration object that tells Zingg which fields to us Each field definition has four attributes: `fieldName` (the column name), `fields` (same as `fieldName` for now), `dataType` (string, integer, double, etc.), and `matchType` (the similarity functions to apply). {% hint style="success" icon="right-long" %} -**Read more:** [Configure Zingg](../../running-zingg/configure-zingg.md) +**Read more:** [Configure Zingg](../../../running-zingg/configure-zingg.md) {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/README.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/README.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/README.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/README.md diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/dont_use-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/dont_use-match.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/dont_use-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/dont_use-match.md diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/email-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/email-match.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/email-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/email-match.md diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/email_optimised-match.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/email_optimised-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/email_optimised-match.md diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/exact-match.md similarity index 97% rename from docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/exact-match.md index 5c7162eb9..edd4a4db2 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/exact-match.md +++ b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/exact-match.md @@ -27,7 +27,7 @@ These fields are reliable unique identifiers. An exact match on SSN means the tw Always use `EXACT` on trusted identifiers. In Zingg Enterprise, also add them as deterministic matching conditions, an exact match on a trusted identifier produces a guaranteed match with score 1 before the probabilistic model runs. -→ [Deterministic vs Probabilistic Matching](../../zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) +→ [Deterministic vs Probabilistic Matching](../../../zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/fuzzy-match.md similarity index 97% rename from docs/zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/fuzzy-match.md index 833e20ebe..f6747a733 100644 --- a/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy-match.md +++ b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/fuzzy-match.md @@ -95,5 +95,5 @@ Use `PINCODE` not `FUZZY` for postal codes. `PINCODE` is built to handle the spe * `ONLY_ALPHABETS_FUZZY` - strip numbers first, then apply fuzzy to letters only * `MAPPING_(FILENAME)` - handle completely different strings (nicknames, abbreviations) that `FUZZY` cannot bridge -**Read more**: [Match Types](./) | [Configure Zingg](../../../running-zingg/configure-zingg.md) | [How Zingg Learns](../) +**Read more**: [Match Types](./) | [Configure Zingg](../../../../running-zingg/configure-zingg.md) | [How Zingg Learns](../../../how-zingg-learns/) {% endhint %} diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/fuzzy_optimised-match.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/fuzzy_optimised-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/fuzzy_optimised-match.md diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/mapping_-filename-match.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/mapping_-filename-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/mapping_-filename-match.md diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/null_or_blank-match.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/null_or_blank-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/null_or_blank-match.md diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/numeric-match.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/numeric-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/numeric-match.md diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/numeric_with_units-match.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/numeric_with_units-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/numeric_with_units-match.md diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_exact-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/only_alphabets_exact-match.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_exact-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/only_alphabets_exact-match.md diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/only_alphabets_fuzzy-match.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/only_alphabets_fuzzy-match.md diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/only_alphabets_fuzzy_optimised-match.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/only_alphabets_fuzzy_optimised-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/only_alphabets_fuzzy_optimised-match.md diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/pincode-match.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/pincode-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/pincode-match.md diff --git a/docs/zingg-concepts/how-zingg-learns/match-types/text-match.md b/docs/zingg-concepts/zingg-configuration/field-definition/match-types/text-match.md similarity index 100% rename from docs/zingg-concepts/how-zingg-learns/match-types/text-match.md rename to docs/zingg-concepts/zingg-configuration/field-definition/match-types/text-match.md diff --git a/docs/zingg-python-api/community-python-api.md b/docs/zingg-python-api/community-python-api.md index c41c6bb7f..fa86fee03 100644 --- a/docs/zingg-python-api/community-python-api.md +++ b/docs/zingg-python-api/community-python-api.md @@ -75,7 +75,7 @@ args.setFieldDefinition(fieldDefs) ``` {% hint style="success" icon="right-long" %} -**Read more**: For all match types and combinations → [Match Types](../zingg-concepts/how-zingg-learns/match-types/) +**Read more**: For all match types and combinations → [Match Types](../zingg-concepts/zingg-configuration/field-definition/match-types/) {% endhint %} ### Configure input and output pipes diff --git a/docs/zingg-python-api/enterprise-zinggec-python-api.md b/docs/zingg-python-api/enterprise-zinggec-python-api.md index af989acdc..380ff7336 100644 --- a/docs/zingg-python-api/enterprise-zinggec-python-api.md +++ b/docs/zingg-python-api/enterprise-zinggec-python-api.md @@ -115,7 +115,7 @@ fname = EFieldDefinition("fname", "string", MatchType.FUZZY, The second argument to `MappingMatchType` is the mapping file name (without extension). The mapping file must be present in your working directory. {% hint style="success" icon="right-long" %} -**Read more**: For the full mapping file format and rules → [Mapping match type](../zingg-concepts/how-zingg-learns/match-types/) +**Read more**: For the full mapping file format and rules → [Mapping match type](../zingg-concepts/zingg-configuration/field-definition/match-types/) {% endhint %} ### Pass through From 251a03f6b56c96a150ceb34037a7bd6edc8c0727 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 19:31:26 +0000 Subject: [PATCH 128/144] GITBOOK-403: No subject --- docs/running-zingg/step-by-step-guide.md | 34 +++++++++++++++++------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/docs/running-zingg/step-by-step-guide.md b/docs/running-zingg/step-by-step-guide.md index 080f49707..1b6af3298 100644 --- a/docs/running-zingg/step-by-step-guide.md +++ b/docs/running-zingg/step-by-step-guide.md @@ -19,23 +19,39 @@ New to entity resolution or want to understand the problem space before diving i {% stepper %} {% step %} -### Connect your data +### Configure -Configure input and output pipes to tell Zingg where your source data lives and where to write results. Zingg connects to the data where it lives - it does not move it. +
+ +Define your data and output {% hint style="success" icon="right-long" %} **Read more**: [Content your Data](../connect-your-data/pipes-and-data-connections.md) {% endhint %} -{% endstep %} -{% step %} -### Configure fields +Configure input and output pipes to tell Zingg where your source data lives and where to write results. Zingg connects to the data where it lives - it does not move it. + +
+ +
-Define your field definitions, match types, model ID, partition count, and relevant Enterprise features (deterministic matching, primary key, pass through, standardization). The configuration drives every downstream phase. +Configure fields {% hint style="success" icon="right-long" %} **Read more**: [Content your Data](../connect-your-data/pipes-and-data-connections.md) {% endhint %} + +Define your field definitions, match types, relevant Enterprise features (deterministic matching, primary key, pass through, standardization). The configuration drives every downstream phase. + +
+ +
+ +Configure model id, number of partitions and labelDataSampleSize + + + +
{% endstep %} {% step %} @@ -52,9 +68,9 @@ Run `findTrainingData` to generate candidate pairs, then label each pair as Matc {% endstep %} {% step %} -### Verify Blocking +### Verify Blocking (Enterprise Only) -Run `verifyBlocking` to check what percentage of your known matching pairs are being blocked together correctly. Run after labelling and before committing to a full training run. If coverage is low, return to labelling. Enterprise Only. +Run `verifyBlocking` to check what percentage of your known matching pairs are being blocked together correctly. Run after labelling and before committing to a full training run. If coverage is low, return to labelling. {% hint style="success" icon="right-long" %} **Read more**: [Verify Blocking](verify-blocking.md) @@ -62,7 +78,7 @@ Run `verifyBlocking` to check what percentage of your known matching pairs are b {% endstep %} {% step %} -### Generate Model Documentation +### Generate Model Documentation (Optional) Run `generateDocs` to produce a human-readable HTML report of your training data, including pairs labeled as matches and non-matches. Useful for sharing with subject-matter experts before training. From 1456926588b086b4d513ef9c08c053df558d3fd5 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 19:32:39 +0000 Subject: [PATCH 129/144] GITBOOK-404: No subject --- docs/running-zingg/experience-zingg.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/running-zingg/experience-zingg.md b/docs/running-zingg/experience-zingg.md index e8467f1bc..cb01d42e9 100644 --- a/docs/running-zingg/experience-zingg.md +++ b/docs/running-zingg/experience-zingg.md @@ -25,19 +25,19 @@ is entity resolution. #### Step 1: Pull the Zingg Docker image ```bash -docker pull zingg/zingg:0.5.0 +docker pull zingg/zingg ``` #### Step 2: Start the container ```bash -docker run -it zingg/zingg:0.5.0 bash +docker run -it zingg/zingg bash ``` If you see a permission error, use: ```bash -docker run -v /tmp:/tmp -it zingg/zingg:0.5.0 bash +docker run -v /tmp:/tmp -it zingg/zingg bash ``` #### Step 3: Run match using the bundled FEBRL sample data and pre-trained model From c095f575a78e88a70ff24fddbe9c9317d19c6085 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Wed, 19 Aug 2026 19:38:38 +0000 Subject: [PATCH 130/144] GITBOOK-405: No subject --- docs/SUMMARY.md | 6 +++--- docs/frequently-asked-questions/concept-glossary.md | 4 ++-- docs/interpreting-results/interpret-output-scores.md | 6 ++---- docs/recipes-and-integration/pre-trained-models.md | 4 ++-- docs/running-zingg/configure-zingg.md | 4 ++-- .../README.md} | 0 .../label-training-pairs.md | 4 ++-- .../{ => create-training-data}/verify-blocking.md | 11 ++++++----- docs/running-zingg/step-by-step-guide.md | 6 +++--- docs/tuning/blocking-strategy.md | 2 +- docs/tuning/improve-accuracy/README.md | 6 +++--- .../improve-accuracy/remove-stopwords-optional.md | 2 +- 12 files changed, 27 insertions(+), 28 deletions(-) rename docs/running-zingg/{create-training-data.md => create-training-data/README.md} (100%) rename docs/running-zingg/{ => create-training-data}/label-training-pairs.md (96%) rename docs/running-zingg/{ => create-training-data}/verify-blocking.md (95%) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 597f6e76b..c97bc9803 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -53,9 +53,9 @@ * [Configure Zingg](running-zingg/configure-zingg.md) * [Set Deterministic Matching Conditions](running-zingg/set-deterministic-matching-conditions.md) * [Pass Through](running-zingg/pass-through.md) -* [Create Training Data](running-zingg/create-training-data.md) -* [Label Training Pairs](running-zingg/label-training-pairs.md) -* [Verify Blocking](running-zingg/verify-blocking.md) +* [Create Training Data](running-zingg/create-training-data/README.md) + * [Label Training Pairs](running-zingg/create-training-data/label-training-pairs.md) + * [Verify Blocking](running-zingg/create-training-data/verify-blocking.md) * [Generate Model Documentation](running-zingg/generate-model-documentation.md) * [Update Label](running-zingg/update-label.md) * [Build and Save the Model](running-zingg/build-and-save-the-model.md) diff --git a/docs/frequently-asked-questions/concept-glossary.md b/docs/frequently-asked-questions/concept-glossary.md index ee8bff033..1665bd04d 100644 --- a/docs/frequently-asked-questions/concept-glossary.md +++ b/docs/frequently-asked-questions/concept-glossary.md @@ -23,7 +23,7 @@ Blocking group records into candidate buckets before any matching begins. Withou Enterprise-scale entity resolution is computationally feasible. {% hint style="success" icon="right-long" %} -**Read more:** [How Zingg learns](../running-zingg/step-by-step-guide.md) | [Verify blocking](../running-zingg/verify-blocking.md) +**Read more:** [How Zingg learns](../running-zingg/step-by-step-guide.md) | [Verify blocking](../running-zingg/create-training-data/verify-blocking.md) {% endhint %} @@ -36,7 +36,7 @@ The process by which Zingg builds its matching model from your feedback rather t typically enough to train a high-accuracy model on datasets of 100,000+ records. {% hint style="success" icon="right-long" %} -**Read more:** [How Zingg learns](../zingg-concepts/how-zingg-learns/) | [Label training pairs](../running-zingg/label-training-pairs.md) +**Read more:** [How Zingg learns](../zingg-concepts/how-zingg-learns/) | [Label training pairs](../running-zingg/create-training-data/label-training-pairs.md) {% endhint %} diff --git a/docs/interpreting-results/interpret-output-scores.md b/docs/interpreting-results/interpret-output-scores.md index dfffa1fe9..9943fde55 100644 --- a/docs/interpreting-results/interpret-output-scores.md +++ b/docs/interpreting-results/interpret-output-scores.md @@ -123,8 +123,8 @@ Ordering clusters by `avg_min` ascending puts the weakest clusters at the top, t **Read more**: * [Interpret Output Scores](interpret-output-scores.md) - `Z_MINSCORE` and `Z_MAXSCORE` explained -* [Label Training Pairs](../running-zingg/label-training-pairs.md) - adding training data for missed patterns -* [Verify blocking](../running-zingg/verify-blocking.md) +* [Label Training Pairs](../running-zingg/create-training-data/label-training-pairs.md) - adding training data for missed patterns +* [Verify blocking](../running-zingg/create-training-data/verify-blocking.md) {% endhint %} A small number of targeted labels for the specific pattern being missed is more effective than a large general labeling run. @@ -184,8 +184,6 @@ How to inspect the similarity model, diagnose false positives and false negative \ The similarity model scores every candidate pair that the blocking model passes through. If your results contain records incorrectly merged into the same cluster (false positives) or matching records that were missed (false negatives), the similarity model is where to investigate after first confirming that blocking is not the cause. - - ### Using scores to decide what to do with clusters
diff --git a/docs/recipes-and-integration/pre-trained-models.md b/docs/recipes-and-integration/pre-trained-models.md index b0b61a61d..8e319838c 100644 --- a/docs/recipes-and-integration/pre-trained-models.md +++ b/docs/recipes-and-integration/pre-trained-models.md @@ -123,8 +123,8 @@ This is faster than building a model from scratch when your data is structurally {% hint style="success" icon="right-long" %} **Read more**: -* Create training data - [Create training data](../running-zingg/create-training-data.md) -* Label training pairs - [Label training pairs](../running-zingg/label-training-pairs.md) +* Create training data - [Create training data](../running-zingg/create-training-data/) +* Label training pairs - [Label training pairs](../running-zingg/create-training-data/label-training-pairs.md) * Build and save the model - [Build and save the model](../running-zingg/build-and-save-the-model.md) {% endhint %} diff --git a/docs/running-zingg/configure-zingg.md b/docs/running-zingg/configure-zingg.md index 027d767f6..bdf5e4534 100644 --- a/docs/running-zingg/configure-zingg.md +++ b/docs/running-zingg/configure-zingg.md @@ -10,8 +10,8 @@ Configuring Zingg is the first step in every Zingg workflow. This page follows n Complete this page before running any other phase. Every phase reads the configuration set here: -* `findTrainingData` → \[[Create Training Data](create-training-data.md)] -* `label` → \[[Label Training Pairs](label-training-pairs.md)] +* `findTrainingData` → \[[Create Training Data](create-training-data/)] +* `label` → \[[Label Training Pairs](create-training-data/label-training-pairs.md)] * `train` → \[[Build and Save the Model](build-and-save-the-model.md)] * `match` → \[[Run the Match Phase](run-the-match-phase.md)] * `link` → \[[Link Across Datasets](link-across-datasets.md)] diff --git a/docs/running-zingg/create-training-data.md b/docs/running-zingg/create-training-data/README.md similarity index 100% rename from docs/running-zingg/create-training-data.md rename to docs/running-zingg/create-training-data/README.md diff --git a/docs/running-zingg/label-training-pairs.md b/docs/running-zingg/create-training-data/label-training-pairs.md similarity index 96% rename from docs/running-zingg/label-training-pairs.md rename to docs/running-zingg/create-training-data/label-training-pairs.md index ea4e7ac4b..d59fa039a 100644 --- a/docs/running-zingg/label-training-pairs.md +++ b/docs/running-zingg/create-training-data/label-training-pairs.md @@ -8,7 +8,7 @@ description: >- The `label` phase opens an interactive layer where you review the candidate pairs found by `findTrainingData` and mark each pair. This is the only step in the Zingg workflow that requires human input. No ML knowledge is needed, just your domain understanding of whether two records represent the same real-world entity. -If you already have labeled data from an external source, you can supply it directly using `trainingSamples` in your configuration. See [Create Training Data](create-training-data.md) for how to set this up. +If you already have labeled data from an external source, you can supply it directly using `trainingSamples` in your configuration. See [Create Training Data](./) for how to set this up. 30 to 40 matching pairs is a strong starting point. Label until you feel that your labeled examples represent all field types and data patterns in your schema. If accuracy needs improvement after your first match run, return to labelling, focus on the patterns or field combinations that appear to be missing or underrepresented. @@ -53,7 +53,7 @@ zingg.initAndExecute() {% endhint %} {% hint style="info" icon="right-long" %} -Need to correct a pair you already labeled? See [Update Label](update-label.md). +Need to correct a pair you already labeled? See [Update Label](../update-label.md). {% endhint %} {% endtab %} diff --git a/docs/running-zingg/verify-blocking.md b/docs/running-zingg/create-training-data/verify-blocking.md similarity index 95% rename from docs/running-zingg/verify-blocking.md rename to docs/running-zingg/create-training-data/verify-blocking.md index 31c6d6e1f..9b9e9f414 100644 --- a/docs/running-zingg/verify-blocking.md +++ b/docs/running-zingg/create-training-data/verify-blocking.md @@ -3,7 +3,8 @@ description: >- Verify that your blocking model is grouping known matching pairs into the same block before training and after. tags: - - enterprise-only + - tag: enterprise-only + primary: true --- # Verify Blocking @@ -92,8 +93,8 @@ influence matching. **Read more**: * [Label Training Pairs](label-training-pairs.md) - how to add more training data -* [Configure Zingg](configure-zingg.md) - changing field match types -* [Blocking Strategies: DEFAULT vs WIDER](../tuning/blocking-strategy.md) - reorder candidate fields in the blocking tree (Enterprise only) +* [Configure Zingg](../configure-zingg.md) - changing field match types +* [Blocking Strategies: DEFAULT vs WIDER](../../tuning/blocking-strategy.md) - reorder candidate fields in the blocking tree (Enterprise only) * [Custom Blocking and Similarity](/broken/pages/DWdyf7az3MmhJaVca3k7) Functions {% endhint %} @@ -171,13 +172,13 @@ If `verifyBlocking` shows that many known matching pairs are not being blocked t 1. **Add more labelled training pairs** of the type being missed. The blocking model learns from the same training data as the similarity model. 2. **Check your field match types.** Fields set to `DONT_USE` are excluded from blocking. If a key identity field is set to `DONT_USE`, matching pairs that differ on other fields may end up in different blocks. -3. **Try the `WIDER` blocking strategy** (Enterprise only) if large blocks suggest the tree is over-relying on one or two fields → [Blocking Strategies: DEFAULT vs WIDER](../tuning/blocking-strategy.md) +3. **Try the `WIDER` blocking strategy** (Enterprise only) if large blocks suggest the tree is over-relying on one or two fields → [Blocking Strategies: DEFAULT vs WIDER](../../tuning/blocking-strategy.md) {% hint style="success" icon="right-long" %} **Consider custom blocking functions** for specialised data patterns → [Custom Blocking and Similarity](/broken/pages/DWdyf7az3MmhJaVca3k7) **Read more**: -* Blocking model concept and how it fits in the pipeline → [Blocking Model](../zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) +* Blocking model concept and how it fits in the pipeline → [Blocking Model](../../zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) * Custom blocking functions for advanced tuning → [Custom Blocking and Similarity](/broken/pages/DWdyf7az3MmhJaVca3k7) {% endhint %} diff --git a/docs/running-zingg/step-by-step-guide.md b/docs/running-zingg/step-by-step-guide.md index 1b6af3298..7e85af96b 100644 --- a/docs/running-zingg/step-by-step-guide.md +++ b/docs/running-zingg/step-by-step-guide.md @@ -62,8 +62,8 @@ Run `findTrainingData` to generate candidate pairs, then label each pair as Matc {% hint style="success" icon="right-long" %} **Read more**: -* [Create Training Data](create-training-data.md) -* [Label Training Pairs](label-training-pairs.md) +* [Create Training Data](create-training-data/) +* [Label Training Pairs](create-training-data/label-training-pairs.md) {% endhint %} {% endstep %} @@ -73,7 +73,7 @@ Run `findTrainingData` to generate candidate pairs, then label each pair as Matc Run `verifyBlocking` to check what percentage of your known matching pairs are being blocked together correctly. Run after labelling and before committing to a full training run. If coverage is low, return to labelling. {% hint style="success" icon="right-long" %} -**Read more**: [Verify Blocking](verify-blocking.md) +**Read more**: [Verify Blocking](create-training-data/verify-blocking.md) {% endhint %} {% endstep %} diff --git a/docs/tuning/blocking-strategy.md b/docs/tuning/blocking-strategy.md index 20e65bbf2..1d98ce38f 100644 --- a/docs/tuning/blocking-strategy.md +++ b/docs/tuning/blocking-strategy.md @@ -74,7 +74,7 @@ args.setBlockingModel("WIDER") **Read more**: * Set up field definitions and the arguments object this key belongs to → [Configure Zingg](../running-zingg/configure-zingg.md) -* Check block-size distribution and coverage after changing strategy → [Verify Blocking](../running-zingg/verify-blocking.md) +* Check block-size distribution and coverage after changing strategy → [Verify Blocking](../running-zingg/create-training-data/verify-blocking.md) * Blocking model concept and how it fits in the pipeline → [Blocking Model](../zingg-concepts/how-zingg-learns/zingg-models/blocking-model.md) * Define your own blocking functions for specialized data patterns → [Custom Blocking and Similarity](/broken/pages/DWdyf7az3MmhJaVca3k7) {% endhint %} diff --git a/docs/tuning/improve-accuracy/README.md b/docs/tuning/improve-accuracy/README.md index 77b3f4fa0..2a365925e 100644 --- a/docs/tuning/improve-accuracy/README.md +++ b/docs/tuning/improve-accuracy/README.md @@ -21,8 +21,8 @@ The most reliable way to improve accuracy is to label more pairs. Return to the {% hint style="success" icon="right-long" %} **Read more**: -* Label training pairs - [Label training pairs](../../running-zingg/label-training-pairs.md) -* Create training data - [Create training data](../../running-zingg/create-training-data.md) +* Label training pairs - [Label training pairs](../../running-zingg/create-training-data/label-training-pairs.md) +* Create training data - [Create training data](../../running-zingg/create-training-data/) {% endhint %} ### Step 2: Check your field match types @@ -50,7 +50,7 @@ Common words like Mr, St, Street, Pvt add noise to fuzzy matching on address and If Zingg is missing matches that you know exist (false negatives), the blocking model may be filtering them out before they even reach the similarity model. Run Verify Blocking to inspect which pairs are being blocked and identify coverage gaps. {% hint style="success" icon="right-long" %} -**Read more**: Verify blocking - [Verify blocking](../../running-zingg/verify-blocking.md) +**Read more**: Verify blocking - [Verify blocking](../../running-zingg/create-training-data/verify-blocking.md) {% endhint %} ### Step 5: Custom blocking and similarity diff --git a/docs/tuning/improve-accuracy/remove-stopwords-optional.md b/docs/tuning/improve-accuracy/remove-stopwords-optional.md index 5f8ec6be1..54eb699c0 100644 --- a/docs/tuning/improve-accuracy/remove-stopwords-optional.md +++ b/docs/tuning/improve-accuracy/remove-stopwords-optional.md @@ -7,7 +7,7 @@ description: >- # Remove Stopwords (Optional) {% hint style="success" icon="right-long" %} -This is an optional step. Run this between [Configure Zingg](../../running-zingg/configure-zingg.md) and [Create Training Data](../../running-zingg/create-training-data.md) for best results. You can run it after matching has already started, but you will need to re-run the match phase for the stopwords to take effect. Most users skip this on the first run. +This is an optional step. Run this between [Configure Zingg](../../running-zingg/configure-zingg.md) and [Create Training Data](../../running-zingg/create-training-data/) for best results. You can run it after matching has already started, but you will need to re-run the match phase for the stopwords to take effect. Most users skip this on the first run. {% endhint %} Common words like `Mr`, `Pvt`, `Av`, `St`, `Street` do not add differential signals and can confuse matching. These are called stopwords. Zingg can recommend which words to treat as stopwords by analyzing your data before training begins. From 21c54963acfab9d07d7babe866b36c357c64808f Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Thu, 20 Aug 2026 04:16:05 +0000 Subject: [PATCH 131/144] GITBOOK-406: No subject --- docs/running-zingg/step-by-step-guide.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/running-zingg/step-by-step-guide.md b/docs/running-zingg/step-by-step-guide.md index 7e85af96b..e351b5d0f 100644 --- a/docs/running-zingg/step-by-step-guide.md +++ b/docs/running-zingg/step-by-step-guide.md @@ -78,9 +78,9 @@ Run `verifyBlocking` to check what percentage of your known matching pairs are b {% endstep %} {% step %} -### Generate Model Documentation (Optional) +### Verify Model (Optional) -Run `generateDocs` to produce a human-readable HTML report of your training data, including pairs labeled as matches and non-matches. Useful for sharing with subject-matter experts before training. +Run `generateDocs` to produce a human-readable HTML report of your training data, including pairs labeled as matches and non-matches. Useful for sharing with subject-matter experts for feedback before training. Run `updateLabel` if needed to incorporate SME comments. {% hint style="success" icon="right-long" %} **Read more**: [Generate Model Documentation](generate-model-documentation.md) @@ -116,19 +116,23 @@ Apply the trained model to your data: {% endstep %} {% step %} -### Interpret Output Scores +### Interpret Output Review output scores to understand match quality. Use `Z_MINSCORE` and `Z_MAXSCORE` to set confidence thresholds for automated vs human-reviewed clusters. +Run `explain` to understand cluster formation and check the output statistics (Enterprise Only) to understand and explain matches. + {% hint style="success" icon="right-long" %} **Read more**: [Interpret Output Scores](../interpreting-results/interpret-output-scores.md) {% endhint %} {% endstep %} {% step %} -### Improve Accuracy (if needed) +### Tune (if needed) + +If match results need improvement, return to find-and-label with focused training data for the patterns being missed, then retrain. Remove stopwords from fields like addresses and company names to improve blocking. -If match results need improvement, return to find-and-label with focused training data for the patterns being missed, then retrain. Remove stopwords from fields like addresses and company names to improve blocking. Use custom blocking and similarity for specialized data patterns. +If the Zingg job is slow, add more labels. The blocking model learns from the labels and adding more matches will help it branch better. {% hint style="success" icon="right-long" %} **Read more**: [Improve Accuracy](../tuning/improve-accuracy/) From 57ee1c510be921cb0ce0ff34e5a19f7b1555159d Mon Sep 17 00:00:00 2001 From: padam Date: Thu, 20 Aug 2026 04:18:32 +0000 Subject: [PATCH 132/144] GITBOOK-407: No subject --- .../interpreting-results/explainability-and-statistics.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/interpreting-results/explainability-and-statistics.md b/docs/interpreting-results/explainability-and-statistics.md index ad722048b..184f4d1f1 100644 --- a/docs/interpreting-results/explainability-and-statistics.md +++ b/docs/interpreting-results/explainability-and-statistics.md @@ -1,7 +1,7 @@ --- description: >- Understand how a specific entity cluster was formed, which records matched, - which did not, and why. + which did not, explore the cluster insights. tags: - ent - tag: enterprise-only @@ -11,7 +11,7 @@ tags: # Explainability and Statistics {% hint style="info" icon="right-long" %} -Enterprise only. The explain phase is not available in Community. +Enterprise only. {% endhint %} Once you have run a match or `runIncremental` phase, you can use the `explain` phase to look under the hood of any cluster. Provide a Zingg ID, and Zingg shows you exactly how that cluster formed, which record pairs were matched probabilistically, and which records did not directly match each other. @@ -25,11 +25,11 @@ in explain output at this time. Clusters formed primarily through deterministic If you get empty results, verify that the Zingg ID you are querying exists in your match output before running explain. {% endhint %} -Explain answers "why did these specific records end up together?" for one cluster at a time. If you instead want aggregate, run-level visibility — how many clusters formed, how dense they are, how much of the matching came from deterministic rules versus probabilistic scoring, and whether clusters are stable across incremental runs — that's what **output statistics** provides, at three levels: a run-wide **Summary**, per-**Cluster** detail, and per-**Record** detail. +Explain answers "why did these specific records end up together?" for one cluster at a time. If you instead want aggregate, run-level visibility , how many clusters formed, how dense they are, how much of the matching came from deterministic rules versus probabilistic scoring, and whether clusters are stable across incremental runs , that's what **output statistics** provides, at three levels: a run-wide **Summary**, per-**Cluster** detail, and per-**Record** detail. {% hint style="success" icon="right-long" %} **Read more**: * For the step-by-step explain phase task with code - [Explain a specific cluster](explain-a-specific-cluster.md) * For record-level and per-cluster match-quality metrics - [Output statistics](output-statistics.md) - {% endhint %} \ No newline at end of file +{% endhint %} From f026722a240fcdaeb454d508e028575033edd4d3 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Thu, 20 Aug 2026 06:13:17 +0000 Subject: [PATCH 133/144] GITBOOK-408: No subject --- docs/SUMMARY.md | 1 + .../streaming-vs-batch-entity-resolution.md | 2 ++ docs/zingg-concepts/zingg-phases.md | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index c97bc9803..11e7b3bef 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -11,6 +11,7 @@ * [Deterministic vs Probabilistic Matching](zingg-concepts/zingg-entity-resolution-platform/deterministic-vs-probabilistic-matching.md) * [Deduplication, linking and resolving](zingg-concepts/zingg-entity-resolution-platform/deduplication-linking-and-resolving.md) * [Incremental Flow](zingg-concepts/zingg-entity-resolution-platform/incremental-run.md) + * [Streaming vs Batch Entity Resolution](zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md) * [How Zingg Learns](zingg-concepts/how-zingg-learns/README.md) * [Zingg Active Learning](zingg-concepts/how-zingg-learns/zingg-active-learning.md) * [Zingg Models](zingg-concepts/how-zingg-learns/zingg-models/README.md) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md b/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md new file mode 100644 index 000000000..1cb8cf633 --- /dev/null +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md @@ -0,0 +1,2 @@ +# Streaming vs Batch Entity Resolution + diff --git a/docs/zingg-concepts/zingg-phases.md b/docs/zingg-concepts/zingg-phases.md index a8f03f7bc..8612bf7dc 100644 --- a/docs/zingg-concepts/zingg-phases.md +++ b/docs/zingg-concepts/zingg-phases.md @@ -72,7 +72,7 @@ Produces HTML documentation of your labeled training pairs and model statistics.
-verifyBlocking +verifyBlocking(Enterprise) Reports what percentage of your known matching pairs are being blocked together correctly. Run after labeling and before training to catch blocking issues early. From ed1ea80c37d3504cc04b510aeeea8985853f3a18 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Thu, 20 Aug 2026 15:15:42 +0000 Subject: [PATCH 134/144] GITBOOK-410: No subject --- .../streaming-vs-batch-entity-resolution.md | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md b/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md index 1cb8cf633..6b4c06cec 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md @@ -1,2 +1,71 @@ +--- +description: Two architectures, one problem +--- + # Streaming vs Batch Entity Resolution +Entity resolution is a problem every organization with more than one data source eventually runs into. What differs is _when_ you need the answer. That single question, more than data volume or record count, is what should decide whether you reach for batch entity resolution, streaming entity resolution, or both. + +This page explains why Zingg supports both architectures natively, what each is actually for, and how to decide which one (or which combination) fits your system. + +### The core distinction: when is identity needed? + +Entity resolution always answers the same question — "is this the same entity I've seen before?" — but systems ask that question at different points in time: + +* **Before a decision is made**, as part of a scheduled process: nightly customer master builds, weekly deduplication runs, periodic golden record refreshes. +* **At the moment a decision is made**, inline with a live event: a fraud check during checkout, a personalization decision on page load, a real-time risk score before a transaction clears. + +Batch entity resolution is built for the first case. Streaming entity resolution is built for the second. Neither is a more evolved version of the other — they're solving for different constraints. + +### Batch entity resolution: the system of record + +Batch ER processes a full dataset (or a large incremental slice of one) at once, producing a consolidated, deduplicated, high-confidence view of entities. It is the right architecture when: + +* **The output is a system of record**, like a master customer, patient, or product dataset that downstream systems depend on being stable and correct. +* **You need full-dataset context.** Batch runs can consider every record against every other record, using the entire history to resolve ambiguous matches that a narrow, real-time window couldn't confidently resolve. +* **Latency is measured in hours or days, not milliseconds**, and correctness matters more than immediacy — MDM, CDP identity spines, compliance and reporting datasets, analytics-ready customer 360 views. + +Batch is the foundation most entity resolution and MDM programs are built on, and it remains the right architecture for the majority of identity workloads today. + +### Streaming entity resolution: identity at the moment of decision + +Streaming ER resolves identity against individual events as they arrive, typically within milliseconds, so that a decision being made _right now_ can use a trusted identity instead of a raw, unresolved record. + +This matters because a growing set of use cases can't wait for the next batch cycle: + +* **Real-time fraud and risk scoring**, where the decision to approve or block happens in the same request that the event arrives in. +* **In-session personalization**, where knowing "this is the same customer who abandoned a cart an hour ago" only has value if it's known before the session ends. +* **Event-driven architectures** (built on Kafka, Confluent, or similar streaming platforms) where identity resolution needs to be a stage in the pipeline itself, not a downstream batch job the pipeline waits on. +* **Operational systems that act on events**, not just report on them — where an unresolved or duplicate identity at decision time means a wrong action taken, not just a dirty report generated later. + +The requirement underneath all of these is the same: **real-time decisioning requires real-time trusted identity.** A batch-resolved golden record that's accurate as of last night's run doesn't help a system that has to act in the next 50 milliseconds. + +### Why not just make batch faster? + +It's tempting to think streaming ER is just batch ER running on a shorter schedule. It isn't, for a structural reason: architectures are built differently in data platforms. + +Streaming entity resolution is different engineering problem (stateful stream processing, incremental identity graphs) rather than a smaller version of the same one. + +### They compose, rather than compete + +In practice, most mature architectures use both, at different stages: + +| | Batch | Streaming | +| --------------------- | -------------------------------------------------- | ----------------------------------------------------------- | +| **Answers** | "What is our authoritative view of this entity?" | "Is this event the same entity as one we already know?" | +| **Latency** | Minutes to hours | Milliseconds to seconds | +| **Context available** | Full dataset | Event + existing identity state | +| **Typical output** | Golden records, MDM, customer 360 | Real-time match/no-match decision, identity-enriched event | +| **Typical consumer** | Analytics, reporting, downstream systems of record | Live application logic, fraud/risk engines, personalization | + +### Choosing an architecture in Zingg + +Zingg supports both batch and streaming entity resolution natively, so the choice is driven by your use case rather than by platform limitations: + +* If your consumers can tolerate latency measured in hours and need the highest-confidence, full-context match — **use batch**. +* If a decision is being made at the moment an event arrives and needs a trusted identity to act on — **use streaming**. +* If you have both kinds of consumers — which most organizations eventually do — **run both**, with streaming handling live decisions and batch maintaining the authoritative identity graph underneath it. + +See \[Batch Entity Resolution: Getting Started] and \[Streaming Entity Resolution: Getting Started] for setup guides for each. + +#### From e7555af1d006dd5146802cb141dfbc6cb7f8db72 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Thu, 20 Aug 2026 15:22:41 +0000 Subject: [PATCH 135/144] GITBOOK-411: No subject --- .../streaming-vs-batch-entity-resolution.md | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md b/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md index 6b4c06cec..81c5726f8 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md @@ -27,7 +27,7 @@ Batch ER processes a full dataset (or a large incremental slice of one) at once, Batch is the foundation most entity resolution and MDM programs are built on, and it remains the right architecture for the majority of identity workloads today. -### Streaming entity resolution: identity at the moment of decision +### Streaming entity resolution: identity at the moment of decision (Enterprise Only) Streaming ER resolves identity against individual events as they arrive, typically within milliseconds, so that a decision being made _right now_ can use a trusted identity instead of a raw, unresolved record. @@ -46,26 +46,15 @@ It's tempting to think streaming ER is just batch ER running on a shorter schedu Streaming entity resolution is different engineering problem (stateful stream processing, incremental identity graphs) rather than a smaller version of the same one. -### They compose, rather than compete +### Why not real time? -In practice, most mature architectures use both, at different stages: - -| | Batch | Streaming | -| --------------------- | -------------------------------------------------- | ----------------------------------------------------------- | -| **Answers** | "What is our authoritative view of this entity?" | "Is this event the same entity as one we already know?" | -| **Latency** | Minutes to hours | Milliseconds to seconds | -| **Context available** | Full dataset | Event + existing identity state | -| **Typical output** | Golden records, MDM, customer 360 | Real-time match/no-match decision, identity-enriched event | -| **Typical consumer** | Analytics, reporting, downstream systems of record | Live application logic, fraud/risk engines, personalization | +Warehouses and datalakes are still not ready for real time design patterns. While there is promising movement towards LTAP/HTAP with Lakebase and Postgres suppoer in Snowflake, we are watching this space keenly and will build something when the technology is right. Going out of the warehouse and supporting real time is one option, but that doesnt make the resolved entities easily consumable throughout the Enterprise. ### Choosing an architecture in Zingg Zingg supports both batch and streaming entity resolution natively, so the choice is driven by your use case rather than by platform limitations: -* If your consumers can tolerate latency measured in hours and need the highest-confidence, full-context match — **use batch**. +* If your pipelines run batch, **use batch**. * If a decision is being made at the moment an event arrives and needs a trusted identity to act on — **use streaming**. -* If you have both kinds of consumers — which most organizations eventually do — **run both**, with streaming handling live decisions and batch maintaining the authoritative identity graph underneath it. - -See \[Batch Entity Resolution: Getting Started] and \[Streaming Entity Resolution: Getting Started] for setup guides for each. #### From 2d50a939b85e8a2a08d86f058a3dd02091345ec1 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Fri, 21 Aug 2026 02:34:48 +0000 Subject: [PATCH 136/144] GITBOOK-412: No subject --- .../streaming-vs-batch-entity-resolution.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md b/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md index 81c5726f8..e7e2291cd 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/streaming-vs-batch-entity-resolution.md @@ -48,7 +48,7 @@ Streaming entity resolution is different engineering problem (stateful stream pr ### Why not real time? -Warehouses and datalakes are still not ready for real time design patterns. While there is promising movement towards LTAP/HTAP with Lakebase and Postgres suppoer in Snowflake, we are watching this space keenly and will build something when the technology is right. Going out of the warehouse and supporting real time is one option, but that doesnt make the resolved entities easily consumable throughout the Enterprise. +Warehouses and datalakes are still not ready for real time design patterns. While there is promising movement towards LTAP/HTAP with Lakebase and Postgres suppoer in Snowflake, we are watching this space keenly and will build something when the technology is right. Going out of the warehouse and supporting real time is one option, but that becomes a separate source of truth with its own ETL and governance. Hence it gets limited to single departments and fails to become the universal source of truth. ### Choosing an architecture in Zingg From 57fbb7d708a0609e6f8334e9398b3381a7567628 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Fri, 21 Aug 2026 03:54:38 +0000 Subject: [PATCH 137/144] GITBOOK-413: No subject --- docs/zingg-concepts/zingg-configuration/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-configuration/README.md b/docs/zingg-concepts/zingg-configuration/README.md index e0bf06d68..a8ecb187e 100644 --- a/docs/zingg-concepts/zingg-configuration/README.md +++ b/docs/zingg-concepts/zingg-configuration/README.md @@ -8,7 +8,7 @@ Here is some important terminology to be aware of: 1. Arguments - representative of the user input comprising of input and output data, fields and their matching criteria, models location as well as performance criteria. Zingg Enterprise also has specialised arguments for functionality like incremental run, reassign and diff etc. 2. Pipes - Zingg's abstraction for the data store. A pipe encapsulates the source or destination of records; a delta file path, a Snowflake table, a UC table, an RDBMS dataset. Different pipes are configured by passing the format string in the configuration. -3. Field Definition - +3. Field Definition - Field Definition is the configuration object that tells Zingg which fields to use for matching and how to compare them. Each field definition has four attributes: `fieldName` (the column name), `fields` (same as `fieldName` for now), `dataType` (string, integer, double, etc.), and `matchType` (the similarity functions to apply). 4. Match type - {% hint style="success" icon="right-long" %} From 7dfcef0862702b36da8b2ec57f5307980c353f55 Mon Sep 17 00:00:00 2001 From: Chandan Date: Sat, 22 Aug 2026 12:03:52 +0530 Subject: [PATCH 138/144] Remove unsupported Spark 3.4 build profile (#1343) The codebase uses Encoders.row(StructType), which only exists in Spark 3.5+, so the spark-3.4 profile could never compile. Remove the dead profile so the build only advertises the Spark version it actually supports. Co-authored-by: chandan Co-authored-by: Sonal --- pom.xml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/pom.xml b/pom.xml index 06da48cb4..041248a54 100644 --- a/pom.xml +++ b/pom.xml @@ -29,22 +29,6 @@ assembly - - spark-3.4 - - - spark - 3.4 - - - - 3.4.0 - 2.12.17 - 3.4 - 2.12 - 0.11.0 - - spark-3.5 From 54c0c4fd3644c05abea5e0705e69f893c9719a97 Mon Sep 17 00:00:00 2001 From: Aditya Pareek <119134401+LOGANBLUE1@users.noreply.github.com> Date: Sat, 22 Aug 2026 12:05:31 +0530 Subject: [PATCH 139/144] added date field and tests (#1314) --- .../zingg/common/core/hash/IdentityDate.java | 20 +++++++++++ .../zingg/common/core/hash/SameMonthYear.java | 19 +++++++++++ .../java/zingg/hash/TestIdentityDate.java | 28 +++++++++++++++ .../java/zingg/hash/TestSameMonthYear.java | 34 +++++++++++++++++++ .../spark/core/hash/SparkSameMonthYear.java | 15 ++++++++ 5 files changed, 116 insertions(+) create mode 100644 common/core/src/main/java/zingg/common/core/hash/IdentityDate.java create mode 100644 common/core/src/main/java/zingg/common/core/hash/SameMonthYear.java create mode 100644 common/core/src/test/java/zingg/hash/TestIdentityDate.java create mode 100644 common/core/src/test/java/zingg/hash/TestSameMonthYear.java create mode 100644 spark/core/src/main/java/zingg/spark/core/hash/SparkSameMonthYear.java diff --git a/common/core/src/main/java/zingg/common/core/hash/IdentityDate.java b/common/core/src/main/java/zingg/common/core/hash/IdentityDate.java new file mode 100644 index 000000000..a0150a844 --- /dev/null +++ b/common/core/src/main/java/zingg/common/core/hash/IdentityDate.java @@ -0,0 +1,20 @@ +package zingg.common.core.hash; +import java.util.Date; +import java.time.LocalDate; +public class IdentityDate extends BaseHash { + + public IdentityDate() { + setName("identityDate"); + } + + @Override + public Integer call(Date date) { + if(date == null) { + return null; + } + int year = date.getYear() + 1900; + int month = date.getMonth() + 1; + int day = date.getDate(); + return year * 10000 + month * 100 + day; + } +} diff --git a/common/core/src/main/java/zingg/common/core/hash/SameMonthYear.java b/common/core/src/main/java/zingg/common/core/hash/SameMonthYear.java new file mode 100644 index 000000000..3fa2e211f --- /dev/null +++ b/common/core/src/main/java/zingg/common/core/hash/SameMonthYear.java @@ -0,0 +1,19 @@ +package zingg.common.core.hash; + +import java.util.Date; + +public class SameMonthYear extends BaseHash{ + public SameMonthYear() { + setName("sameMonthYear"); + } + + @Override + public Integer call(Date date) { + if(date == null) { + return null; + } + int year = date.getYear() + 1900; + int month = date.getMonth() + 1; + return year * 100 + month; + } +} diff --git a/common/core/src/test/java/zingg/hash/TestIdentityDate.java b/common/core/src/test/java/zingg/hash/TestIdentityDate.java new file mode 100644 index 000000000..a30c2d31e --- /dev/null +++ b/common/core/src/test/java/zingg/hash/TestIdentityDate.java @@ -0,0 +1,28 @@ +package zingg.hash; + +import org.junit.jupiter.api.Test; +import zingg.common.core.hash.IdentityDate; +import zingg.common.core.hash.IdentityInteger; + +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestIdentityDate { + + @Test + public void testIdentityInteger() { + IdentityDate value = getInstance(); + assertEquals(20020624, value.call(new Date("2002/06/24"))); + } + + @Test + public void testNullValue() { + IdentityDate value = getInstance(); + assertEquals(null, value.call(null)); + } + + private IdentityDate getInstance() { + return new IdentityDate(); + } +} diff --git a/common/core/src/test/java/zingg/hash/TestSameMonthYear.java b/common/core/src/test/java/zingg/hash/TestSameMonthYear.java new file mode 100644 index 000000000..db60ff4f2 --- /dev/null +++ b/common/core/src/test/java/zingg/hash/TestSameMonthYear.java @@ -0,0 +1,34 @@ +package zingg.hash; + +import java.util.Date; +import org.junit.jupiter.api.Test; +import zingg.common.core.hash.SameMonthYear; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestSameMonthYear { + @Test + public void testSameMonthYear() { + SameMonthYear value = getInstance(); + assertEquals(200206, value.call(new Date("2002/06/24"))); + } + + @Test + public void testSameMonthYear2() { + SameMonthYear value = getInstance(); + assertEquals(value.call(new Date("2002/06/11")), + value.call(new Date("2002/06/24"))); + } + + @Test + public void testNullValue() { + SameMonthYear value = getInstance(); + assertEquals(null, value.call(null)); + } + + private SameMonthYear getInstance() { + return new SameMonthYear(); + } + + +} diff --git a/spark/core/src/main/java/zingg/spark/core/hash/SparkSameMonthYear.java b/spark/core/src/main/java/zingg/spark/core/hash/SparkSameMonthYear.java new file mode 100644 index 000000000..936831bf1 --- /dev/null +++ b/spark/core/src/main/java/zingg/spark/core/hash/SparkSameMonthYear.java @@ -0,0 +1,15 @@ +package zingg.spark.core.hash; + +import org.apache.spark.sql.types.DataTypes; +import zingg.common.core.hash.SameMonthYear; + +import java.util.Date; + +public class SparkSameMonthYear extends SparkHashFunction { + public SparkSameMonthYear() { + setBaseHash(new SameMonthYear()); + setDataType(DataTypes.DateType); + setReturnType(DataTypes.IntegerType); + } +} + From 4f067fdfc13fb2586956c81ed8f3df367e566cbe Mon Sep 17 00:00:00 2001 From: Chandan Date: Sat, 22 Aug 2026 12:59:51 +0530 Subject: [PATCH 140/144] Use native Spark regexp_replace for stopword removal instead of a UDF (#1337) Fixes #1080 Co-authored-by: chandan --- .../preprocess/stopwords/RemoveStopWords.java | 27 --------- .../core/preprocess/stopwords/StopWords.java | 58 ------------------- .../stopwords/RemoveStopWordsUDF.java | 21 ------- .../stopwords/SparkStopWordsRemover.java | 22 +------ 4 files changed, 2 insertions(+), 126 deletions(-) delete mode 100644 common/core/src/main/java/zingg/common/core/preprocess/stopwords/RemoveStopWords.java delete mode 100644 common/core/src/main/java/zingg/common/core/preprocess/stopwords/StopWords.java delete mode 100644 spark/core/src/main/java/zingg/spark/core/preprocess/stopwords/RemoveStopWordsUDF.java diff --git a/common/core/src/main/java/zingg/common/core/preprocess/stopwords/RemoveStopWords.java b/common/core/src/main/java/zingg/common/core/preprocess/stopwords/RemoveStopWords.java deleted file mode 100644 index f0c95e1aa..000000000 --- a/common/core/src/main/java/zingg/common/core/preprocess/stopwords/RemoveStopWords.java +++ /dev/null @@ -1,27 +0,0 @@ -package zingg.common.core.preprocess.stopwords; - -import java.io.Serializable; - -public class RemoveStopWords implements Serializable { - - private static final long serialVersionUID = 1L; - private String name = "removeStopWordsUDF"; - - public RemoveStopWords() { - super(); - } - - protected String removeStopWordsUsingRegex(String s,String stopWordsRegexString) { - if (s == null || stopWordsRegexString==null) return null; - return s.replaceAll(stopWordsRegexString, ""); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - -} diff --git a/common/core/src/main/java/zingg/common/core/preprocess/stopwords/StopWords.java b/common/core/src/main/java/zingg/common/core/preprocess/stopwords/StopWords.java deleted file mode 100644 index e7b6407d2..000000000 --- a/common/core/src/main/java/zingg/common/core/preprocess/stopwords/StopWords.java +++ /dev/null @@ -1,58 +0,0 @@ -package zingg.common.core.preprocess.stopwords; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import zingg.common.client.arguments.model.IArguments; -import zingg.common.client.ZFrame; -import zingg.common.client.ZinggClientException; -import zingg.common.client.util.ColName; -import zingg.common.client.util.PipeUtilBase; - -public class StopWords { - - protected static String name = "zingg.preprocess.stopwords.StopWords"; - public static final Log LOG = LogFactory.getLog(StopWords.class); - protected static String stopWordColumn = ColName.COL_WORD; - protected static final int COLUMN_INDEX_DEFAULT = 0; - protected PipeUtilBase pipeUtil; - - public PipeUtilBase getPipeUtil() { - return pipeUtil; - } - - public void setPipeUtil(PipeUtilBase pipeUtil) { - this.pipeUtil = pipeUtil; - } - - - - public ZFrame preprocessForStopWords(S session, IArguments args, ZFrame ds) throws ZinggClientException { - /* - List wordList = new ArrayList(); - for (FieldDefinition def : args.getFieldDefinition()) { - if (!(def.getStopWords() == null || def.getStopWords() == "")) { - ZFrame stopWords = getPipeUtil().read(false, false, getPipeUtil().getStopWordsPipe(args, def.getStopWords())); - //if (!Arrays.asList(stopWords.schema().fieldNames()).contains(stopWordColumn)) { - stopWordColumn = stopWords.columns()[COLUMN_INDEX_DEFAULT]; - //} - wordList = stopWords.select(stopWordColumn).as(Encoders.STRING()).collectAsList(); - String pattern = wordList.stream().collect(Collectors.joining("|", "\\b(", ")\\b\\s?")); - ds = ds.withColumn(def.getFieldName(), removeStopWords(pattern.toLowerCase()).apply(ds.col(def.getFieldName()))); - } - } - - return ds; - */ - return ds; - } - - /* - public static UserDefinedFunction removeStopWords(String stopWordsRegexString) { - return udf((String s) -> { - if (s == null) return null; - return s.toLowerCase().replaceAll(stopWordsRegexString, ""); - }, DataTypes.StringType); - } - */ -} diff --git a/spark/core/src/main/java/zingg/spark/core/preprocess/stopwords/RemoveStopWordsUDF.java b/spark/core/src/main/java/zingg/spark/core/preprocess/stopwords/RemoveStopWordsUDF.java deleted file mode 100644 index 2e9943b98..000000000 --- a/spark/core/src/main/java/zingg/spark/core/preprocess/stopwords/RemoveStopWordsUDF.java +++ /dev/null @@ -1,21 +0,0 @@ -package zingg.spark.core.preprocess.stopwords; - -import org.apache.spark.sql.api.java.UDF2; - -import zingg.common.core.preprocess.stopwords.RemoveStopWords; - -public class RemoveStopWordsUDF extends RemoveStopWords implements UDF2{ - - private static final long serialVersionUID = 1L; - - public RemoveStopWordsUDF() { - super(); - } - - @Override - public String call(String s,String stopWordsRegexString) throws Exception { - return removeStopWordsUsingRegex(s,stopWordsRegexString); - } - -} - diff --git a/spark/core/src/main/java/zingg/spark/core/preprocess/stopwords/SparkStopWordsRemover.java b/spark/core/src/main/java/zingg/spark/core/preprocess/stopwords/SparkStopWordsRemover.java index dcac47762..36c7dc9b1 100644 --- a/spark/core/src/main/java/zingg/spark/core/preprocess/stopwords/SparkStopWordsRemover.java +++ b/spark/core/src/main/java/zingg/spark/core/preprocess/stopwords/SparkStopWordsRemover.java @@ -1,7 +1,6 @@ package zingg.spark.core.preprocess.stopwords; -import static org.apache.spark.sql.functions.callUDF; -import static org.apache.spark.sql.functions.lit; +import static org.apache.spark.sql.functions.regexp_replace; import java.io.Serializable; @@ -11,7 +10,6 @@ import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; import org.apache.spark.sql.types.DataType; -import org.apache.spark.sql.types.DataTypes; import zingg.common.client.FieldDefinition; import zingg.common.client.ZFrame; @@ -19,50 +17,34 @@ import zingg.common.core.preprocess.stopwords.StopWordsRemover; import zingg.spark.client.SparkFrame; import org.apache.spark.sql.SparkSession; -import zingg.spark.core.util.SparkFnRegistrar; public class SparkStopWordsRemover extends StopWordsRemover,Row,Column,DataType> implements Serializable { private static final long serialVersionUID = 1L; protected static String name = "zingg.spark.preprocess.SparkStopWordsRemover"; public static final Log LOG = LogFactory.getLog(SparkStopWordsRemover.class); - - private String udfName; public SparkStopWordsRemover(){ } public SparkStopWordsRemover(IContext, Row, Column,DataType> context) { super(context); - registerUDF(); } public SparkStopWordsRemover(IContext, Row, Column,DataType> context, FieldDefinition fd) { super(context,fd); - registerUDF(); } @Override protected ZFrame, Row, Column> removeStopWordsFromDF(ZFrame, Row, Column> ds, String fieldName, String pattern) { - Dataset dfAfterRemoval = ds.df().withColumn(fieldName,callUDF(udfName, ds.df().col(fieldName),lit(pattern))); + Dataset dfAfterRemoval = ds.df().withColumn(fieldName, regexp_replace(ds.df().col(fieldName), pattern, "")); return new SparkFrame(dfAfterRemoval); } - protected void registerUDF() { - RemoveStopWordsUDF removeStopWordsUDF = new RemoveStopWordsUDF(); - // Each field will have different pattern - this.udfName = removeStopWordsUDF.getName(); - // register the UDF - SparkSession zSession = getContext().getSession(); - - SparkFnRegistrar.registerUDF2(zSession, udfName, removeStopWordsUDF, DataTypes.StringType); - } - @Override public void init() { - registerUDF(); } } From 3ccd7a5a27ff84dedb89fcc64fd41104024dac8e Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Sun, 23 Aug 2026 18:11:32 +0000 Subject: [PATCH 141/144] GITBOOK-415: No subject --- ...g-is-built-to-solve-and-why-they-are-harder-than-it-looks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index e77f4a67a..bce7c60cd 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -14,7 +14,7 @@ Entity resolution at scale is not a data quality problem. It is an algorithmic c | **The pairs are not enough problem** |

After the similarity model scores every candidate pair, a graph algorithm groups them into complete clusters using transitive closure.

If Record A matches Record B, and Record B matches Record C, the algorithm concludes that A, B, and C all represent the same entity and groups them into a single cluster, even if A and C were never directly compared.

This is what turns a list of scored pairs into a usable identity graph. Every resolved entity becomes a node. Every cluster is an entity resolved across all its representations.

In Community, clusters are assigned a Z Cluster that is non-persistent between runs. In Enterprise, each cluster is assigned a persistent Zingg ID (GUID) that remains stable across runs, incremental updates, and model changes.

| | **Managing addition, updation and deletion of records** |

Production entity resolution rarely sees a static dataset. New records arrive daily; sometimes hourly. The naive approach is to rerun the full match each time new data comes in. This breaks at scale for two reasons. First, every re-run produces fresh cluster IDs. Downstream systems that store the cluster ID as a customer or entity key break every time the match is re-run.

Zingg Enterprise's runIncremental phase handles this. New records are evaluated against the existing identity graph. Records matching an existing cluster inherit its Zingg ID. Records that do not match any existing cluster receive a new Zingg ID. The existing graph stays intact. Downstream systems keep working.

Second, re-running the full match is computationally expensive and slows down your pipelines. Doing it every time a thousand new records arrive is not the best way to process data.

Read more:

| | **The data drift problem - why models decay** |

Data patterns change. New source systems are added. Naming conventions shift. A model trained on last year's data may perform poorly on this year's records, not because the model was wrong, but because the variation distribution in your data changed.

Most systems have no mechanism to detect or adapt to these changes. Zingg Enterprise's compare model results feature (diff phase) lets you benchmark a retrained model against your current production model before deploying. So, you can see exactly what changed before the change goes live. Zingg Enterprise also provides reassign feature, so you can continue with existing ZINGG IDs even while upgrading models.Read more:

| -| **The governance gap** | Explaining how records matched or why they did not match is critical for audit and compliance, espeically in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching. | +| **The governance gap** | Explaining how records matched or why they did not match is critical for audit and compliance, especially in regulated industries. Zingg Enterprise comes with a whole suit of explanations at the record and cluster level to help stakeholders understand the matching. | | **The domain experience gap** |

Some records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)

| | **The schema problem** |

Since matching is tied heavily to fields, most tools end up imposing restrictions on attributes which can be present in the input. Zingg takes a completely different approach. By learning directly from the data, Zingg works with any fields and their combinations, with no hard requirements for mandatory email, phone number or other fields.

Zingg's approach also powers any entity type like supplier, product etc, not just customer.

| | **The data privacy problem** | Entity resolution is typically performed on the most sensitive data woned by the Enterprise. Customer lists, Vendor Info, Leads. Sending such data to a third party or a public cloud service is a major privacy and compliance nightmare. Through warehouse native entity resolution, Zingg ensures your data remains private. | From ffd714783e18ad1016152e3fc537411dc25662e2 Mon Sep 17 00:00:00 2001 From: Sonal Goyal Date: Sun, 23 Aug 2026 18:12:33 +0000 Subject: [PATCH 142/144] GITBOOK-416: No subject --- ...g-is-built-to-solve-and-why-they-are-harder-than-it-looks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md index bce7c60cd..5939627c4 100644 --- a/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md +++ b/docs/zingg-concepts/zingg-entity-resolution-platform/the-problems-zingg-is-built-to-solve-and-why-they-are-harder-than-it-looks.md @@ -18,7 +18,7 @@ Entity resolution at scale is not a data quality problem. It is an algorithmic c | **The domain experience gap** |

Some records need special treatment. For example Beth, Eliza and Betty. Same person, different nicknames. Or IBM and International Business Machine. Zingg's mapping framework promotes domain expert supplied dictionaries to push matching accuracy further.

Sometimes records contain information that is not sharp enough for meaningful signals - like common words Pvt, Ltd etc. Zingg recommends and allows domain expert to plugin the relevant stopwords to ignore during matching.

Together with the SME labeling the edge cases, this provides unmatched matching accuracy(pun intended 😄)

| | **The schema problem** |

Since matching is tied heavily to fields, most tools end up imposing restrictions on attributes which can be present in the input. Zingg takes a completely different approach. By learning directly from the data, Zingg works with any fields and their combinations, with no hard requirements for mandatory email, phone number or other fields.

Zingg's approach also powers any entity type like supplier, product etc, not just customer.

| | **The data privacy problem** | Entity resolution is typically performed on the most sensitive data woned by the Enterprise. Customer lists, Vendor Info, Leads. Sending such data to a third party or a public cloud service is a major privacy and compliance nightmare. Through warehouse native entity resolution, Zingg ensures your data remains private. | -| **The data pipeline problem** |

Entity Resolution is a fundamental construct in data management and has to align with the enterprise's overall architecture needs.

Zingg's design in all editions ensures entity resolution can run part of your data proessing pipeline, at a cadence of your choice.

Zingg Enterprise provides batch and streaming flows to cater to different architecture choices and design patterns.

By running directly within the data pieline, Zingg maximises data infrastructure ROI since no extra ETL and observability needs to be set for Zingg.

| +| **The data stack architecture problem** |

Entity Resolution is a fundamental construct in data management and has to align with the enterprise's overall architecture needs.

Zingg's design in all editions ensures entity resolution can run part of your data proessing pipeline, at a cadence of your choice.

Zingg Enterprise provides batch and streaming flows to cater to different architecture choices and design patterns.

By running directly within the data pieline, Zingg maximises data infrastructure ROI since no extra ETL and observability needs to be set for Zingg.

| | | |
From 9d31ba11f2a69ac1404dce26a33e87aed490857b Mon Sep 17 00:00:00 2001 From: chandan Date: Mon, 24 Aug 2026 20:26:27 +0530 Subject: [PATCH 143/144] Auto-generate dependabot.yml from pom dependency scopes Derive the Dependabot config from the single source of truth (the poms) instead of hand-maintaining it: - scripts/gen-dependabot.py scans every pom, emitting an allow list for compile-scope (shipped) deps and an ignore list for provided-scope deps (Spark/Scala/Jackson-core) the runtime supplies. Version placeholders are wildcarded; a dep that is provided in any module is treated as provided (mirrors Dependabot's ignore-wins-over-allow rule). - .github/dependabot.yml regenerated from the script (also fixes the invalid package-ecosystem: "*" that had been silently rejecting the whole file). - A pre-commit hook and CI workflow run the script in --check mode so the committed config can never drift from the poms. Co-Authored-By: Claude Opus 4.8 --- .github/dependabot.yml | 34 +++- .github/workflows/dependabot-config.yml | 29 +++ .pre-commit-config.yaml | 18 ++ scripts/gen-dependabot.py | 227 ++++++++++++++++++++++++ 4 files changed, 301 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/dependabot-config.yml create mode 100644 .pre-commit-config.yaml create mode 100644 scripts/gen-dependabot.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3307e47c5..4a3192ffd 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,11 +1,31 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - +# --------------------------------------------------------------------------- +# AUTO-GENERATED by scripts/gen-dependabot.py from the poms' dependency scopes. +# Do not edit by hand — run the script and commit the result. +# allow (include) = `compile`-scope deps we actually ship -> watch these +# ignore (exclude) = `provided`-scope deps supplied by the runtime -> noise +# A dep that is `provided` in any module is treated as provided (ignore wins). +# --------------------------------------------------------------------------- version: 2 updates: - - package-ecosystem: "*" # See documentation for possible values - directory: "/" # Location of package manifests + - package-ecosystem: "maven" + directories: + - "/**" # root + all Maven submodules schedule: interval: "weekly" + open-pull-requests-limit: 10 + allow: + - dependency-name: "com.fasterxml.jackson.core:jackson-annotations" + - dependency-name: "com.fasterxml.jackson.module:jackson-module-scala_2.12" + - dependency-name: "com.wcohen.ss:secondstring" + - dependency-name: "commons-logging:commons-logging" + - dependency-name: "io.graphframes:graphframes-spark3_2.12" + - dependency-name: "javax.mail:mail" + - dependency-name: "org.apache.httpcomponents:httpclient" + - dependency-name: "org.freemarker:freemarker" + ignore: + - dependency-name: "com.fasterxml.jackson.core:jackson-core" + - dependency-name: "com.fasterxml.jackson.core:jackson-databind" + - dependency-name: "org.apache.spark:spark-core_*" + - dependency-name: "org.apache.spark:spark-graphx_*" + - dependency-name: "org.apache.spark:spark-mllib_*" + - dependency-name: "org.scala-lang:scala-library" diff --git a/.github/workflows/dependabot-config.yml b/.github/workflows/dependabot-config.yml new file mode 100644 index 000000000..aa10cc59f --- /dev/null +++ b/.github/workflows/dependabot-config.yml @@ -0,0 +1,29 @@ +name: dependabot-config + +# Ensures .github/dependabot.yml stays in sync with the poms' dependency scopes. +# The file is generated by scripts/gen-dependabot.py; this fails the build if a +# pom changed but the config wasn't regenerated. +on: + pull_request: + paths: + - '**/pom.xml' + - '.github/dependabot.yml' + - 'scripts/gen-dependabot.py' + - '.github/workflows/dependabot-config.yml' + push: + branches: [ main ] + paths: + - '**/pom.xml' + - '.github/dependabot.yml' + - 'scripts/gen-dependabot.py' + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Verify dependabot.yml matches the poms + run: python3 scripts/gen-dependabot.py --check diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..1b7ef7777 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace # remove spaces at the end of the line + - id: end-of-file-fixer # file ends with exactly one new line + - id: check-yaml + - id: check-merge-conflict + + - repo: local + hooks: + - id: dependabot-config + name: dependabot.yml is generated from the poms + entry: python3 scripts/gen-dependabot.py --check + language: system + pass_filenames: false + # Re-check whenever a pom or the generated config changes. + files: '(pom\.xml|\.github/dependabot\.yml|scripts/gen-dependabot\.py)$' diff --git a/scripts/gen-dependabot.py b/scripts/gen-dependabot.py new file mode 100644 index 000000000..132afa31f --- /dev/null +++ b/scripts/gen-dependabot.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +"""Generate .github/dependabot.yml from the project's poms. + +Zingg compiles against Spark/Scala/Jackson-core which are `provided` at runtime +(the cluster supplies them) and therefore not shipped in our jar. Dependabot +version-bump PRs for those are pure noise. Rather than hand-maintaining the +config, we derive it from the single source of truth — the dependency scopes +declared across the poms: + + * allow (include) <- `compile`-scope deps we actually ship -> watch these + * ignore (exclude) <- `provided`-scope deps the runtime supplies -> silence + +Usage: + python3 scripts/gen-dependabot.py # rewrite .github/dependabot.yml + python3 scripts/gen-dependabot.py --check # exit 1 if the file is stale + +-------------------------------------------------------------------------- +HOW THIS FILE IS ORGANIZED (read top to bottom, it tells a story): + + STEP 1 — Imports & configuration : the tools we use, and constants + STEP 2 — Small helpers : tiny reusable pieces of logic + STEP 3 — Scan the poms : read every pom.xml, sort deps + STEP 4 — Render the YAML : turn the sorted deps into text + STEP 5 — main(): tie it all together : the actual program entry point +-------------------------------------------------------------------------- +""" + +# ============================================================================= +# STEP 1 — Imports & configuration +# ============================================================================= + +# glob: lets us search the filesystem for files matching a pattern (here, "**/pom.xml") +import glob +# re: Python's regular expression module, used to find/replace text patterns +import re +# sys: gives us access to command-line arguments and the ability to exit with a status code +import sys +# ElementTree: a built-in library for reading and parsing XML files (pom.xml is XML) +import xml.etree.ElementTree as ET + +# Maven pom.xml files declare a "namespace" — this tells ElementTree how to find +# tags like even though the file's real tag name is prefixed internally. +NS = {"m": "http://maven.apache.org/POM/4.0.0"} + +# Path to the file this script writes/checks. +DEPENDABOT_YML = ".github/dependabot.yml" + + +# ============================================================================= +# STEP 2 — Small helpers +# ============================================================================= +# These two functions don't do anything by themselves — they're small building +# blocks used later by collect_by_scope() in STEP 3. + +def _text(el): + """Safely get the text of an XML element, or None if it's missing/empty.""" + # Small helper: given an XML element, safely return its text content + # (trimmed of whitespace), or None if the element doesn't exist or is empty. + # This avoids repeating "if el is not None" checks everywhere else. + return el.text.strip() if el is not None and el.text else None + + +def _pattern(group, artifact): + """`group:artifact`, with any Maven property in the artifactId (e.g. + `${scala.binary.version}`) turned into a `*` wildcard so the pattern matches + whatever the active build profile resolves it to.""" + # Maven poms sometimes use placeholders like ${scala.binary.version} instead + # of a literal version number. Replace anything inside ${...} with a "*" + # wildcard, since Dependabot patterns support wildcards but not variables. + artifact = re.sub(r"\$\{[^}]+\}", "*", artifact) + # Dependabot dependency-name patterns look like "groupId:artifactId". + return f"{group}:{artifact}" + + +# ============================================================================= +# STEP 3 — Scan the poms +# ============================================================================= + +def collect_by_scope(): + """Scan every pom and bucket external (non-zingg) deps by Maven scope. + Returns (provided, shipped) as sorted pattern lists, where: + provided = scope 'provided' — supplied by the runtime, not in our jar + shipped = scope 'compile' — actually packaged and shipped + A dependency that appears as `provided` in ANY module is treated as provided + (it drops out of `shipped`), because Dependabot's `ignore` wins over `allow`, + so listing it in both would just get it ignored anyway.""" + + # --- 3a. Set up empty buckets ------------------------------------------- + # Use sets (not lists) so duplicate dependencies across multiple poms are + # automatically collapsed into one entry. + provided, compile_ = set(), set() + + # --- 3b. Find every pom.xml in the project ------------------------------ + for pom in glob.glob("**/pom.xml", recursive=True): + # Skip poms inside build output folders (e.g. target/classes/...), + # we only care about the real source poms. + if "/target/" in pom: + continue + try: + # Parse the XML file into a tree we can search through. + root = ET.parse(pom).getroot() + except ET.ParseError as ex: + # If a pom is malformed, warn but keep going instead of crashing. + print(f"WARN: could not parse {pom}: {ex}", file=sys.stderr) + continue + + # --- 3c. Look at every tag in this pom ----------------- + for dep in root.iter("{http://maven.apache.org/POM/4.0.0}dependency"): + group = _text(dep.find("m:groupId", NS)) + artifact = _text(dep.find("m:artifactId", NS)) + # Skip incomplete entries, and skip Zingg's own internal modules + # (we only want *external* third-party dependencies). + if not group or not artifact or group.startswith("zingg"): + continue + # If no tag is present, Maven's default scope is "compile". + scope = _text(dep.find("m:scope", NS)) or "compile" + pat = _pattern(group, artifact) + if scope == "provided": + provided.add(pat) + elif scope == "compile": + compile_.add(pat) + + # --- 3d. Resolve conflicts: "provided" always wins ---------------------- + # If a dependency is "provided" in even one module, treat it as provided + # everywhere, removing it from the "shipped" set. This mirrors how + # Dependabot itself behaves: its ignore list wins over its allow list. + shipped = compile_ - provided # provided-anywhere wins + + # --- 3e. Return sorted, stable results ----------------------------------- + # Return both lists sorted, so the generated YAML file has a stable, + # predictable order (important so re-running the script doesn't create + # noisy diffs when nothing actually changed). + return sorted(provided), sorted(shipped) + + +# ============================================================================= +# STEP 4 — Render the YAML +# ============================================================================= + +def render(provided, shipped): + """Turn the (provided, shipped) dependency lists into dependabot.yml text.""" + + # --- 4a. Fixed header + boilerplate config ------------------------------- + # Build the dependabot.yml file line by line as a list of strings, then + # join them together at the end. This is a common Python pattern for + # generating text files. + lines = [ + "# ---------------------------------------------------------------------------", + "# AUTO-GENERATED by scripts/gen-dependabot.py from the poms' dependency scopes.", + "# Do not edit by hand — run the script and commit the result.", + "# allow (include) = `compile`-scope deps we actually ship -> watch these", + "# ignore (exclude) = `provided`-scope deps supplied by the runtime -> noise", + "# A dep that is `provided` in any module is treated as provided (ignore wins).", + "# ---------------------------------------------------------------------------", + "version: 2", + "updates:", + ' - package-ecosystem: "maven"', + " directories:", + ' - "/**" # root + all Maven submodules', + " schedule:", + ' interval: "weekly"', + " open-pull-requests-limit: 10", + " allow:", + ] + + # --- 4b. One "allow" line per shipped dependency ------------------------- + # These are the deps we actually ship, so Dependabot should watch them. + for p in shipped: + lines.append(f' - dependency-name: "{p}"') + + # --- 4c. One "ignore" line per provided dependency ----------------------- + # These are supplied by the runtime, so version-bump PRs for them are noise. + lines.append(" ignore:") + for p in provided: + lines.append(f' - dependency-name: "{p}"') + + # --- 4d. Combine everything into one final string ------------------------ + # Join every line with a newline, plus one trailing newline at the end + # of the file (standard convention for text files). + return "\n".join(lines) + "\n" + + +# ============================================================================= +# STEP 5 — main(): tie it all together +# ============================================================================= + +def main(): + # --- 5a. Figure out which mode we're running in -------------------------- + # If the script was run as "gen-dependabot.py --check", we only verify + # the file is up to date instead of overwriting it. + check = "--check" in sys.argv + + # --- 5b. Do the real work: scan poms, then build the YAML text ----------- + provided, shipped = collect_by_scope() + content = render(provided, shipped) + + # --- 5c. Read whatever is currently on disk (for comparison) ------------- + # Try to read whatever is currently on disk, so we can compare it to the + # freshly generated content. If the file doesn't exist yet, treat it as + # "nothing to compare" (current = None) instead of crashing. + try: + with open(DEPENDABOT_YML) as fh: + current = fh.read() + except FileNotFoundError: + current = None + + # --- 5d. --check mode: report only, never write -------------------------- + if check: + # --check mode: don't write anything. Just report whether the + # committed file matches what the script would generate right now. + if current != content: + print(f"{DEPENDABOT_YML} is stale. Run: python3 scripts/gen-dependabot.py") + sys.exit(1) # non-zero exit code = failure, useful for CI + print(f"{DEPENDABOT_YML} is up to date.") + return + + # --- 5e. Normal mode: actually write the file ----------------------------- + with open(DEPENDABOT_YML, "w") as fh: + fh.write(content) + print(f"Wrote {DEPENDABOT_YML}: {len(shipped)} allowed, {len(provided)} ignored.") + + +# This is the standard Python idiom for "only run main() if this file was +# executed directly (e.g. `python3 gen-dependabot.py`), not if it were +# imported as a module from another script." +if __name__ == "__main__": + main() From 4dd966cbff9fbf3c1d833a35c13fa4e321349aba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:17:17 +0000 Subject: [PATCH 144/144] Bump org.jacoco:jacoco-maven-plugin from 0.8.6 to 0.8.15 in /spark Bumps [org.jacoco:jacoco-maven-plugin](https://github.com/jacoco/jacoco) from 0.8.6 to 0.8.15. - [Release notes](https://github.com/jacoco/jacoco/releases) - [Commits](https://github.com/jacoco/jacoco/compare/v0.8.6...v0.8.15) --- updated-dependencies: - dependency-name: org.jacoco:jacoco-maven-plugin dependency-version: 0.8.15 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 041248a54..515934aa4 100644 --- a/pom.xml +++ b/pom.xml @@ -207,7 +207,7 @@ org.jacoco jacoco-maven-plugin - 0.8.6 + 0.8.15 prepare-agent