whitebox-dedup 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- whitebox_dedup-0.1.0/.github/workflows/pages.yml +34 -0
- whitebox_dedup-0.1.0/.github/workflows/publish.yml +60 -0
- whitebox_dedup-0.1.0/.gitignore +25 -0
- whitebox_dedup-0.1.0/CONTRIBUTING.md +106 -0
- whitebox_dedup-0.1.0/PKG-INFO +294 -0
- whitebox_dedup-0.1.0/README.md +273 -0
- whitebox_dedup-0.1.0/docs/index.html +1099 -0
- whitebox_dedup-0.1.0/docs/mars-equation.md +31 -0
- whitebox_dedup-0.1.0/docs/quickstart.md +56 -0
- whitebox_dedup-0.1.0/models/README.md +117 -0
- whitebox_dedup-0.1.0/models/calibration/text_only_v1_icp.json +12 -0
- whitebox_dedup-0.1.0/models/calibration/text_phone_geo_v1_icp.json +12 -0
- whitebox_dedup-0.1.0/models/calibration/text_phone_v1_icp.json +12 -0
- whitebox_dedup-0.1.0/models/text_only_v1.json +13 -0
- whitebox_dedup-0.1.0/models/text_phone_geo_v1.json +14 -0
- whitebox_dedup-0.1.0/models/text_phone_v1.json +14 -0
- whitebox_dedup-0.1.0/packages/javascript/package.json +31 -0
- whitebox_dedup-0.1.0/packages/javascript/src/conformal.ts +31 -0
- whitebox_dedup-0.1.0/packages/javascript/src/features.ts +107 -0
- whitebox_dedup-0.1.0/packages/javascript/src/index.ts +9 -0
- whitebox_dedup-0.1.0/packages/javascript/src/mars_scorer.ts +108 -0
- whitebox_dedup-0.1.0/packages/javascript/src/pipeline.ts +81 -0
- whitebox_dedup-0.1.0/packages/javascript/src/router.ts +94 -0
- whitebox_dedup-0.1.0/packages/javascript/tsconfig.json +16 -0
- whitebox_dedup-0.1.0/pyproject.toml +31 -0
- whitebox_dedup-0.1.0/tests/__init__.py +0 -0
- whitebox_dedup-0.1.0/tests/fixtures/__init__.py +0 -0
- whitebox_dedup-0.1.0/tests/fixtures/generate_fixtures.py +334 -0
- whitebox_dedup-0.1.0/tests/fixtures/pairs_farmer_labelled.csv +101 -0
- whitebox_dedup-0.1.0/tests/fixtures/pairs_nin_labelled.csv +61 -0
- whitebox_dedup-0.1.0/tests/fixtures/registry_farmer_200.csv +201 -0
- whitebox_dedup-0.1.0/tests/fixtures/registry_nin_300.csv +296 -0
- whitebox_dedup-0.1.0/tests/test_conformal.py +115 -0
- whitebox_dedup-0.1.0/tests/test_features.py +252 -0
- whitebox_dedup-0.1.0/tests/test_mars_scorer.py +174 -0
- whitebox_dedup-0.1.0/tests/test_pipeline.py +191 -0
- whitebox_dedup-0.1.0/tests/test_router.py +179 -0
- whitebox_dedup-0.1.0/tests/test_schema_infer.py +248 -0
- whitebox_dedup-0.1.0/tests/test_trainer.py +34 -0
- whitebox_dedup-0.1.0/whitebox_dedup/__init__.py +27 -0
- whitebox_dedup-0.1.0/whitebox_dedup/__main__.py +5 -0
- whitebox_dedup-0.1.0/whitebox_dedup/blocking.py +115 -0
- whitebox_dedup-0.1.0/whitebox_dedup/cli.py +199 -0
- whitebox_dedup-0.1.0/whitebox_dedup/conformal.py +96 -0
- whitebox_dedup-0.1.0/whitebox_dedup/features.py +166 -0
- whitebox_dedup-0.1.0/whitebox_dedup/mars_scorer.py +180 -0
- whitebox_dedup-0.1.0/whitebox_dedup/pipeline.py +164 -0
- whitebox_dedup-0.1.0/whitebox_dedup/router.py +145 -0
- whitebox_dedup-0.1.0/whitebox_dedup/schema.py +168 -0
- whitebox_dedup-0.1.0/whitebox_dedup/trainer.py +194 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Deploy Playground to GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: "pages"
|
|
15
|
+
cancel-in-progress: false
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
deploy:
|
|
19
|
+
environment:
|
|
20
|
+
name: github-pages
|
|
21
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- name: Checkout
|
|
25
|
+
uses: actions/checkout@v4
|
|
26
|
+
- name: Setup Pages
|
|
27
|
+
uses: actions/configure-pages@v5
|
|
28
|
+
- name: Upload artifact
|
|
29
|
+
uses: actions/upload-pages-artifact@v3
|
|
30
|
+
with:
|
|
31
|
+
path: 'docs'
|
|
32
|
+
- name: Deploy to GitHub Pages
|
|
33
|
+
id: deployment
|
|
34
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: Publish Packages (PyPI & npm)
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
pypi-publish:
|
|
10
|
+
name: Build and publish Python package to PyPI
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write # Required for PyPI Trusted Publishing OIDC
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout repository
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.11"
|
|
22
|
+
|
|
23
|
+
- name: Install build dependencies
|
|
24
|
+
run: python -m pip install --upgrade pip build twine
|
|
25
|
+
|
|
26
|
+
- name: Build wheel and source tarball
|
|
27
|
+
run: python -m build
|
|
28
|
+
|
|
29
|
+
- name: Publish to PyPI
|
|
30
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
31
|
+
with:
|
|
32
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
33
|
+
skip-existing: true
|
|
34
|
+
|
|
35
|
+
npm-publish:
|
|
36
|
+
name: Build and publish JavaScript package to npm
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
defaults:
|
|
39
|
+
run:
|
|
40
|
+
working-directory: packages/javascript
|
|
41
|
+
steps:
|
|
42
|
+
- name: Checkout repository
|
|
43
|
+
uses: actions/checkout@v4
|
|
44
|
+
|
|
45
|
+
- name: Set up Node.js
|
|
46
|
+
uses: actions/setup-node@v4
|
|
47
|
+
with:
|
|
48
|
+
node-version: "20.x"
|
|
49
|
+
registry-url: "https://registry.npmjs.org"
|
|
50
|
+
|
|
51
|
+
- name: Install dependencies
|
|
52
|
+
run: npm ci || npm install
|
|
53
|
+
|
|
54
|
+
- name: Build TypeScript
|
|
55
|
+
run: npm run build
|
|
56
|
+
|
|
57
|
+
- name: Publish to npm
|
|
58
|
+
run: npm publish --access public
|
|
59
|
+
env:
|
|
60
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
*.so
|
|
5
|
+
.Python
|
|
6
|
+
build/
|
|
7
|
+
develop-eggs/
|
|
8
|
+
dist/
|
|
9
|
+
downloads/
|
|
10
|
+
eggs/
|
|
11
|
+
.eggs/
|
|
12
|
+
lib/
|
|
13
|
+
lib64/
|
|
14
|
+
parts/
|
|
15
|
+
sdist/
|
|
16
|
+
var/
|
|
17
|
+
wheels/
|
|
18
|
+
*.egg-info/
|
|
19
|
+
.installed.cfg
|
|
20
|
+
*.egg
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.coverage
|
|
23
|
+
htmlcov/
|
|
24
|
+
.DS_Store
|
|
25
|
+
results.json
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Contributing to whitebox-dedup
|
|
2
|
+
|
|
3
|
+
Contributions to `whitebox-dedup` are welcome. This document covers the workflow,
|
|
4
|
+
code standards, and the non-negotiable data privacy rule.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Getting Started
|
|
9
|
+
|
|
10
|
+
1. Fork the repository and clone your fork.
|
|
11
|
+
2. Install dev dependencies:
|
|
12
|
+
```bash
|
|
13
|
+
pip install -e ".[dev]"
|
|
14
|
+
```
|
|
15
|
+
3. Run the test suite to confirm a clean baseline:
|
|
16
|
+
```bash
|
|
17
|
+
pytest tests/
|
|
18
|
+
```
|
|
19
|
+
4. Create a feature branch and open a pull request against `main`.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Code Standards
|
|
24
|
+
|
|
25
|
+
- Python 3.10+ syntax only.
|
|
26
|
+
- `jellyfish` is the only string similarity library permitted. Do not add `python-Levenshtein`,
|
|
27
|
+
`fuzzywuzzy`, or `rapidfuzz`.
|
|
28
|
+
- Do not import `pyearth`. The MARS model is implemented as pure arithmetic in
|
|
29
|
+
`mars_scorer.py`. `scikit-learn` is an optional training-only dependency.
|
|
30
|
+
- No `scikit-learn` imports in any file under `whitebox_dedup/`. Only `trainer.py` may use it.
|
|
31
|
+
- All new features must have corresponding tests in `tests/`.
|
|
32
|
+
- Run `ruff check .` before submitting a pull request.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Data Privacy Rule
|
|
37
|
+
|
|
38
|
+
**Real beneficiary records must never be committed to this repository.**
|
|
39
|
+
|
|
40
|
+
This rule is absolute and applies to every contributor without exception.
|
|
41
|
+
|
|
42
|
+
### What is permitted in version control
|
|
43
|
+
|
|
44
|
+
- Synthetic fixture files generated by `tests/fixtures/generate_fixtures.py`
|
|
45
|
+
- Model weight JSON files in `models/` (coefficients only, no training records)
|
|
46
|
+
- ICP calibration JSON files in `models/calibration/`
|
|
47
|
+
- Anonymised aggregate statistics (counts, percentages, summary tables)
|
|
48
|
+
|
|
49
|
+
### What is prohibited
|
|
50
|
+
|
|
51
|
+
- Any CSV, Excel, or database export containing real names, phone numbers, GPS coordinates,
|
|
52
|
+
national ID numbers, dates of birth, or any other personally identifiable information
|
|
53
|
+
- Any file derived from real programme beneficiary data, even if partially anonymised
|
|
54
|
+
- Trained model files that embed individual-level information in their weights
|
|
55
|
+
|
|
56
|
+
### Why this rule exists
|
|
57
|
+
|
|
58
|
+
The training data for `text_only_v1` derives from agricultural programme beneficiary
|
|
59
|
+
registries collected in Nigeria. Those records belong to programme participants who
|
|
60
|
+
did not consent to their data being published in an open-source repository.
|
|
61
|
+
Publishing them would violate data protection obligations and the trust of programme
|
|
62
|
+
participants.
|
|
63
|
+
|
|
64
|
+
### Enforcement
|
|
65
|
+
|
|
66
|
+
Pull requests that include real personal data will be rejected immediately and the
|
|
67
|
+
data will be purged from the repository history. If you are unsure whether a file
|
|
68
|
+
contains personal data, do not commit it. Ask first.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Adding a New Pre-trained Model
|
|
73
|
+
|
|
74
|
+
1. Train your model using `trainer.py` with your own labelled pairs.
|
|
75
|
+
2. Export the JSON file to `models/your_model_name.json`.
|
|
76
|
+
3. If you have a calibration set, export the ICP calibration to
|
|
77
|
+
`models/calibration/your_model_name_icp.json`.
|
|
78
|
+
4. Update `models/README.md` with a description of the model.
|
|
79
|
+
5. Add the model name to `DeduplicationPipeline._MODEL_SELECTION_ORDER`
|
|
80
|
+
in `whitebox_dedup/pipeline.py` with its required feature set.
|
|
81
|
+
6. Add tests in `tests/test_pipeline.py` covering model selection.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Regenerating Test Fixtures
|
|
86
|
+
|
|
87
|
+
If you modify the fixture generator, regenerate all fixtures before committing:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
python tests/fixtures/generate_fixtures.py
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The seed is fixed at 42. The output must be deterministic. All 125 tests must
|
|
94
|
+
still pass after regeneration.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Release Process
|
|
99
|
+
|
|
100
|
+
Releases are tagged on `main`. The version in `pyproject.toml` must be updated
|
|
101
|
+
before tagging. The wheel is built with:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip install hatchling
|
|
105
|
+
python -m hatchling build
|
|
106
|
+
```
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: whitebox-dedup
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interpretable entity deduplication with auditable MARS equations and conformal prediction
|
|
5
|
+
Author-email: Daniel Olagunju <d.olagunju@futuxconsult.com>, Temitope Olarewaju <t.olarewaju@futuxconsult.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: Africa,MARS,conformal prediction,data governance,deduplication,entity resolution
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: jellyfish>=1.0
|
|
10
|
+
Requires-Dist: pandas>=2.0
|
|
11
|
+
Requires-Dist: pyyaml>=6.0
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: faker>=24.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
17
|
+
Provides-Extra: train
|
|
18
|
+
Requires-Dist: numpy>=1.26; extra == 'train'
|
|
19
|
+
Requires-Dist: scikit-learn>=1.4; extra == 'train'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# whitebox-dedup
|
|
23
|
+
|
|
24
|
+
Interpretable entity deduplication using auditable MARS equations and Inductive Conformal Prediction, with a flexible feature registry that adapts to whatever columns the data has.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quick Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install whitebox-dedup
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
First result in three lines:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from whitebox_dedup import DeduplicationPipeline
|
|
38
|
+
pipeline = DeduplicationPipeline.from_csv("farmers.csv")
|
|
39
|
+
print(pipeline.run())
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
CLI equivalent:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
wdedup deduplicate --input farmers.csv --output results.json
|
|
46
|
+
wdedup show-equation --model text_only_v1
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Why It Exists
|
|
52
|
+
|
|
53
|
+
Deduplication in African programme registries (farmer benefit lists, NIN databases, voter rolls) has historically been delegated to black-box ML models that produce a score nobody can audit. When a beneficiary challenges a duplicate flag, the programme officer has no arithmetic to show them. Courts and auditors cannot verify what the system did.
|
|
54
|
+
|
|
55
|
+
FutuX Agri-consult Limited built this library after observing the same pattern across 12 agricultural programmes in Nigeria between 2021 and 2026: opaque deduplication created downstream fraud disputes that consumed more resources than the duplicates themselves. The solution is not a more powerful model. The solution is a model whose decision can be written on one page and checked by a field officer with a calculator.
|
|
56
|
+
|
|
57
|
+
The MARS equation is that page. Every hinge term, its knot, and its coefficient are stored in a plain JSON file that any regulator can read. The conformal prediction layer adds a formal coverage guarantee: when the model cannot decide, it says so explicitly and routes the pair to a human. Automated decisions are made only when the statistical evidence is strong enough to sustain audit.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## How It Works
|
|
62
|
+
|
|
63
|
+
### Feature Registry
|
|
64
|
+
|
|
65
|
+
Every feature the engine can compute is registered with the columns it requires. Features whose columns are absent are silently skipped. The engine adapts to whatever schema the user has.
|
|
66
|
+
|
|
67
|
+
| Feature | Required columns | Description |
|
|
68
|
+
|------------------|-------------------------------|--------------------------------------------------|
|
|
69
|
+
| `jw_name_full` | any NAME column | Jaro-Winkler on full concatenated name |
|
|
70
|
+
| `jw_name_first` | any NAME column | Jaro-Winkler on first token only |
|
|
71
|
+
| `jw_name_last` | any NAME column | Jaro-Winkler on last token only |
|
|
72
|
+
| `exact_id` | any ID column | Exact string match after normalisation |
|
|
73
|
+
| `exact_phone` | any PHONE column | Exact match after stripping formatting |
|
|
74
|
+
| `haversine_m` | LATITUDE + LONGITUDE | Great-circle distance in metres |
|
|
75
|
+
| `same_admin_1` | any ADMIN_1 column | Same state/region/province |
|
|
76
|
+
| `same_admin_2` | any ADMIN_2 column | Same LGA/district/ward |
|
|
77
|
+
| `dob_match` | any DATE_OF_BIRTH column | Exact DOB string match |
|
|
78
|
+
| `same_gender` | any GENDER column | Same gender (normalised to first letter) |
|
|
79
|
+
| `same_enumerator`| any ENUMERATOR column | Same field agent collected both records |
|
|
80
|
+
|
|
81
|
+
### Three-Tier Decision Routing
|
|
82
|
+
|
|
83
|
+
Every candidate pair is assigned to exactly one tier:
|
|
84
|
+
|
|
85
|
+
| Tier | Condition | Action |
|
|
86
|
+
|-------------------|-----------------------------------------------------|---------------------------------|
|
|
87
|
+
| 1: AUTO_MERGE | High-confidence duplicate (or hard ID match) | System merges automatically |
|
|
88
|
+
| 2: HUMAN_REVIEW | Ambiguous: conformal set is {0, 1} | Diverted to human administrator |
|
|
89
|
+
| 3: AUTO_DISTINCT | High-confidence distinct (or hard ID mismatch) | No action taken |
|
|
90
|
+
|
|
91
|
+
Hard signals short-circuit the MARS model entirely:
|
|
92
|
+
- Exact unique ID match: Tier 1 immediately
|
|
93
|
+
- Exact unique ID mismatch (both non-null): Tier 3 immediately
|
|
94
|
+
- Exact phone match + JW >= 0.70: Tier 1
|
|
95
|
+
- DOB exact match + JW >= 0.85: Tier 1
|
|
96
|
+
|
|
97
|
+
### The MARS Equation
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
logit[P(duplicate)] = -102.231
|
|
101
|
+
+ 562.965 * max(0, jw_name_full - 0.559)
|
|
102
|
+
- 679.490 * max(0, jw_name_full - 0.806)
|
|
103
|
+
+ 96.830 * max(0, 0.898 - jw_name_full)
|
|
104
|
+
+ 122.623 * max(0, jw_name_full - 0.898)
|
|
105
|
+
|
|
106
|
+
P(duplicate) = 1 / (1 + exp(-logit))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This is the complete model. No hidden layers. No embeddings. A spreadsheet is sufficient to verify any decision.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Pre-trained Models
|
|
114
|
+
|
|
115
|
+
| Model name | Required features | Use case |
|
|
116
|
+
|---------------------|---------------------------------------|-------------------------------------|
|
|
117
|
+
| `text_only_v1` | `jw_name_full` | Name-only registries (NIN, voter) |
|
|
118
|
+
| `text_phone_v1` | `jw_name_full`, `exact_phone` | Name + phone (credit applicants) |
|
|
119
|
+
| `text_phone_geo_v1` | `jw_name_full`, `exact_phone`, GPS | Agricultural rosters with GPS |
|
|
120
|
+
|
|
121
|
+
`DeduplicationPipeline` selects the best available model automatically.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Flexible Column Guide
|
|
126
|
+
|
|
127
|
+
### NIN Registry (name + NIN + DOB + gender, no GPS)
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from whitebox_dedup.schema import EntitySchema, EntityField, FieldRole
|
|
131
|
+
schema = EntitySchema(fields={
|
|
132
|
+
"nin": EntityField("nin", FieldRole.ID, unique_key=True),
|
|
133
|
+
"full_name": EntityField("full_name", FieldRole.NAME),
|
|
134
|
+
"date_of_birth": EntityField("date_of_birth", FieldRole.DATE_OF_BIRTH),
|
|
135
|
+
"gender": EntityField("gender", FieldRole.GENDER),
|
|
136
|
+
"state_origin": EntityField("state_origin", FieldRole.ADMIN_1),
|
|
137
|
+
})
|
|
138
|
+
from whitebox_dedup import DeduplicationPipeline
|
|
139
|
+
pipeline = DeduplicationPipeline.from_schema(schema, "nin_records.csv")
|
|
140
|
+
results = pipeline.run()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Active features: `exact_id`, `jw_name_full`, `jw_name_first`, `jw_name_last`, `dob_match`, `same_gender`, `same_admin_1`
|
|
144
|
+
|
|
145
|
+
### Agricultural Roster (name + phone + GPS)
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
schema = EntitySchema.infer("farmers.csv") # auto-detect all columns
|
|
149
|
+
pipeline = DeduplicationPipeline.from_csv("farmers.csv", schema=schema)
|
|
150
|
+
results = pipeline.run()
|
|
151
|
+
print(results.summary())
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Patient Registry (hospital_id + name + DOB)
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
schema = EntitySchema(fields={
|
|
158
|
+
"hospital_id": EntityField("hospital_id", FieldRole.ID, unique_key=True),
|
|
159
|
+
"full_name": EntityField("full_name", FieldRole.NAME),
|
|
160
|
+
"dob": EntityField("dob", FieldRole.DATE_OF_BIRTH),
|
|
161
|
+
"gender": EntityField("gender", FieldRole.GENDER),
|
|
162
|
+
})
|
|
163
|
+
pipeline = DeduplicationPipeline.from_schema(schema, "patients.csv")
|
|
164
|
+
results = pipeline.run()
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Python API Reference
|
|
170
|
+
|
|
171
|
+
### `DeduplicationPipeline`
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
DeduplicationPipeline.from_csv(path, schema=None)
|
|
175
|
+
# Load CSV, infer or apply schema, select best model.
|
|
176
|
+
|
|
177
|
+
DeduplicationPipeline.from_schema(schema, source)
|
|
178
|
+
# Explicit schema with CSV path or DataFrame.
|
|
179
|
+
|
|
180
|
+
pipeline.run() -> DeduplicationResult
|
|
181
|
+
# Run blocking + feature extraction + routing.
|
|
182
|
+
|
|
183
|
+
pipeline.score_pair(row_a: dict, row_b: dict) -> RoutingResult
|
|
184
|
+
# Score one pair without full blocking.
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### `EntitySchema`
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
EntitySchema.infer(source) # Auto-detect from CSV or DataFrame
|
|
191
|
+
EntitySchema.from_yaml(path) # Load from YAML config file
|
|
192
|
+
EntitySchema.from_dict(data) # Load from Python dict
|
|
193
|
+
schema.active_features() # Set of computable feature names
|
|
194
|
+
schema.fields_by_role(FieldRole.X) # Column names for a given role
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### `MARSScorer`
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
MARSScorer.load_pretrained("text_only_v1")
|
|
201
|
+
MARSScorer.load(path)
|
|
202
|
+
scorer.score(features: dict) -> float # Probability in [0, 1]
|
|
203
|
+
scorer.score_with_trace(features) -> dict # Full arithmetic trace
|
|
204
|
+
scorer.equation_text() -> str # Human-readable equation
|
|
205
|
+
scorer.save(path) # Export to JSON
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### `DeduplicationResult`
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
result.summary() -> dict
|
|
212
|
+
result.tier1_auto_merge # list of pair dicts
|
|
213
|
+
result.tier2_human_review # list of pair dicts
|
|
214
|
+
result.tier3_auto_distinct # list of pair dicts
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## CLI Reference
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# Deduplicate a CSV file
|
|
223
|
+
wdedup deduplicate --input farmers.csv [--schema schema.yaml] [--output results.json]
|
|
224
|
+
|
|
225
|
+
# Score a single pair interactively
|
|
226
|
+
wdedup score-pair [--schema schema.yaml]
|
|
227
|
+
|
|
228
|
+
# Print the MARS equation
|
|
229
|
+
wdedup show-equation [--model text_only_v1]
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Training Your Own Model
|
|
235
|
+
|
|
236
|
+
Training requires the optional `train` extras:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
pip install "whitebox-dedup[train]"
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Prepare a labelled pairs CSV with a binary `is_duplicate` column and feature columns. Then run `trainer.py`:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
python trainer.py \
|
|
246
|
+
--labelled-pairs my_labelled_pairs.csv \
|
|
247
|
+
--label-col is_duplicate \
|
|
248
|
+
--feature-cols jw_name_full \
|
|
249
|
+
--output models/my_custom_model.json
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The trainer fits a MARS model (scikit-learn pipeline), extracts the piecewise linear terms, and serialises them to the JSON format that `MARSScorer` loads. The output file is standalone: no scikit-learn dependency at inference time.
|
|
253
|
+
|
|
254
|
+
See `models/README.md` for a full explanation of the coefficient format.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## JavaScript Package
|
|
259
|
+
|
|
260
|
+
A companion JavaScript/TypeScript package for browser and Node.js environments is planned. It will load the same JSON model files and run identical arithmetic, enabling client-side auditing without a Python runtime.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Contributing
|
|
265
|
+
|
|
266
|
+
Contributions are welcome. Please read `CONTRIBUTING.md` before opening a pull request, especially the Data Privacy Rule.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Data Privacy Rule
|
|
271
|
+
|
|
272
|
+
Real beneficiary records are never committed to this repository. Only synthetic fixtures (generated by `tests/fixtures/generate_fixtures.py`) and model weight JSON files are committed. See `CONTRIBUTING.md` for the full policy.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
MIT. Copyright FutuX Agri-consult Limited.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Citation
|
|
283
|
+
|
|
284
|
+
If this library contributes to published research, please cite:
|
|
285
|
+
|
|
286
|
+
```bibtex
|
|
287
|
+
@software{whitebox_dedup_2024,
|
|
288
|
+
author = {{FutuX Agri-consult Limited}},
|
|
289
|
+
title = {whitebox-dedup: Interpretable entity deduplication with MARS equations},
|
|
290
|
+
year = {2024},
|
|
291
|
+
url = {https://github.com/futux/whitebox-dedup-ml},
|
|
292
|
+
version = {0.1.0}
|
|
293
|
+
}
|
|
294
|
+
```
|