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.
Files changed (50) hide show
  1. whitebox_dedup-0.1.0/.github/workflows/pages.yml +34 -0
  2. whitebox_dedup-0.1.0/.github/workflows/publish.yml +60 -0
  3. whitebox_dedup-0.1.0/.gitignore +25 -0
  4. whitebox_dedup-0.1.0/CONTRIBUTING.md +106 -0
  5. whitebox_dedup-0.1.0/PKG-INFO +294 -0
  6. whitebox_dedup-0.1.0/README.md +273 -0
  7. whitebox_dedup-0.1.0/docs/index.html +1099 -0
  8. whitebox_dedup-0.1.0/docs/mars-equation.md +31 -0
  9. whitebox_dedup-0.1.0/docs/quickstart.md +56 -0
  10. whitebox_dedup-0.1.0/models/README.md +117 -0
  11. whitebox_dedup-0.1.0/models/calibration/text_only_v1_icp.json +12 -0
  12. whitebox_dedup-0.1.0/models/calibration/text_phone_geo_v1_icp.json +12 -0
  13. whitebox_dedup-0.1.0/models/calibration/text_phone_v1_icp.json +12 -0
  14. whitebox_dedup-0.1.0/models/text_only_v1.json +13 -0
  15. whitebox_dedup-0.1.0/models/text_phone_geo_v1.json +14 -0
  16. whitebox_dedup-0.1.0/models/text_phone_v1.json +14 -0
  17. whitebox_dedup-0.1.0/packages/javascript/package.json +31 -0
  18. whitebox_dedup-0.1.0/packages/javascript/src/conformal.ts +31 -0
  19. whitebox_dedup-0.1.0/packages/javascript/src/features.ts +107 -0
  20. whitebox_dedup-0.1.0/packages/javascript/src/index.ts +9 -0
  21. whitebox_dedup-0.1.0/packages/javascript/src/mars_scorer.ts +108 -0
  22. whitebox_dedup-0.1.0/packages/javascript/src/pipeline.ts +81 -0
  23. whitebox_dedup-0.1.0/packages/javascript/src/router.ts +94 -0
  24. whitebox_dedup-0.1.0/packages/javascript/tsconfig.json +16 -0
  25. whitebox_dedup-0.1.0/pyproject.toml +31 -0
  26. whitebox_dedup-0.1.0/tests/__init__.py +0 -0
  27. whitebox_dedup-0.1.0/tests/fixtures/__init__.py +0 -0
  28. whitebox_dedup-0.1.0/tests/fixtures/generate_fixtures.py +334 -0
  29. whitebox_dedup-0.1.0/tests/fixtures/pairs_farmer_labelled.csv +101 -0
  30. whitebox_dedup-0.1.0/tests/fixtures/pairs_nin_labelled.csv +61 -0
  31. whitebox_dedup-0.1.0/tests/fixtures/registry_farmer_200.csv +201 -0
  32. whitebox_dedup-0.1.0/tests/fixtures/registry_nin_300.csv +296 -0
  33. whitebox_dedup-0.1.0/tests/test_conformal.py +115 -0
  34. whitebox_dedup-0.1.0/tests/test_features.py +252 -0
  35. whitebox_dedup-0.1.0/tests/test_mars_scorer.py +174 -0
  36. whitebox_dedup-0.1.0/tests/test_pipeline.py +191 -0
  37. whitebox_dedup-0.1.0/tests/test_router.py +179 -0
  38. whitebox_dedup-0.1.0/tests/test_schema_infer.py +248 -0
  39. whitebox_dedup-0.1.0/tests/test_trainer.py +34 -0
  40. whitebox_dedup-0.1.0/whitebox_dedup/__init__.py +27 -0
  41. whitebox_dedup-0.1.0/whitebox_dedup/__main__.py +5 -0
  42. whitebox_dedup-0.1.0/whitebox_dedup/blocking.py +115 -0
  43. whitebox_dedup-0.1.0/whitebox_dedup/cli.py +199 -0
  44. whitebox_dedup-0.1.0/whitebox_dedup/conformal.py +96 -0
  45. whitebox_dedup-0.1.0/whitebox_dedup/features.py +166 -0
  46. whitebox_dedup-0.1.0/whitebox_dedup/mars_scorer.py +180 -0
  47. whitebox_dedup-0.1.0/whitebox_dedup/pipeline.py +164 -0
  48. whitebox_dedup-0.1.0/whitebox_dedup/router.py +145 -0
  49. whitebox_dedup-0.1.0/whitebox_dedup/schema.py +168 -0
  50. 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
+ ```