Skip to main content

indic-namematch

CI PyPI Python License: MIT

Matching person names across Indian identity documents.

The same person's name is written differently on a PAN card, an Aadhaar, a passport and a bank statement. It gets abbreviated to initials, reordered surname-first, transliterated with a different spelling, stripped of a middle name, prefixed with an honorific, or mangled by OCR. General-purpose fuzzy matching handles this badly, because it has no idea that half of North India shares a surname.

from indic_namematch import NameMatcher

m = NameMatcher()

m.score("Kumar Suresh", "Suresh Kumar")        # 1.000  surname-first ordering
m.score("Lakshmi Narayanan", "Laxmi Narayanan")# 0.975  transliteration
m.score("Smt. Sunita Devi", "Sunita Devi")     # 1.000  honorific
m.score("Ram Kumar", "Ramkumar")               # 1.000  token boundary

m.score("Rajesh Kumar Sharma", "Ramesh Kumar Sharma")  # 0.886  two brothers
m.score("S. Kumar", "Sunita Kumar")                    # 0.653  not the same person

Pure standard library. No dependencies.

pip install indic-namematch

Three bands, not two

Production KYC does not answer match or no-match. It answers auto-approve, send to a human, or auto-reject, and the middle band is where a matcher should decline rather than guess.

from indic_namematch import NameMatcher, Decision

m = NameMatcher()
m.decide("Kumar Suresh", "Suresh Kumar")               # Decision.APPROVE
m.decide("Rajesh Kumar Sharma", "Ramesh Kumar Sharma") # Decision.REVIEW
m.decide("Amit Verma", "Fatima Sheikh")                # Decision.REJECT

This is not a stylistic choice. Some pairs are genuinely undecidable from the strings: two people are both named Amit Kumar, and no function of two identical strings can separate them from a real match on two identical strings. Since nothing can score higher than an exact match, refusing to ever produce a false positive means auto-approving nobody. The honest design routes the uncertainty to a human instead of pretending it away.

Rarity weighting

Agreeing on Kumar proves almost nothing. Agreeing on Padmanabhan nearly settles it. This is Fellegi & Sunter (1969), and it is the difference between auto-approving two brothers and not.

The bundled table is a coarse hand-curated tier list. Replace it with frequencies from your own records and the weights become measured rather than assumed:

from indic_namematch import NameMatcher, RarityTable

table = RarityTable.from_counts({"kumar": 50_000, "sharma": 9_000, "padmanabhan": 12})
m = NameMatcher(rarity=table)

RarityTable.from_csv("token_counts.csv")   # two columns: token, count
RarityTable.uniform()                      # disable rarity weighting entirely

Explaining a score

The first question anyone asks is "why did it score that". So:

print(m.explain("Rajesh Kumar Sharma", "Ramesh Kumar Sharma"))
'Rajesh Kumar Sharma' vs 'Ramesh Kumar Sharma'
  normalised : 'rajesh kumar sharma' | 'ramesh kumar sharma'
  kumar ~ kumar: 1.00 (identical) x weight 0.15 = 0.150
  sharma ~ sharma: 1.00 (identical) x weight 0.40 = 0.400
  rajesh ~ ramesh: 0.73 (character) x weight 0.40 = 0.292
  score 0.886 -> REVIEW

kumar matched perfectly and contributed 0.150. That is the whole idea.

Command line

indic-namematch "S. Kumar" "Suresh Kumar"
indic-namematch "Lakshmi Narayanan" "Laxmi Narayanan" --explain
indic-namematch "Rajesh Kumar Sharma" "Ramesh Kumar Sharma" --all

What it handles

example score
initials expanded S. Kumar / Suresh Kumar 0.653
surname-first ordering Kumar Suresh / Suresh Kumar 1.000
transliteration Lakshmi Narayanan / Laxmi Narayanan 0.975
dropped middle name Priya Lakshmi Menon / Priya Menon 0.615
honorifics and suffixes Smt. Sunita Devi / Sunita Devi 1.000
relationship markers Manjunath S/o Ramaiah / Manjunath Ramaiah 1.000
patronymic initials Nithya B / Nithya Balakrishnan 0.716
token boundaries Ram Kumar / Ramkumar 1.000
OCR damage Balaji Iyengar / Ba1aji Iyengar 0.864

And, just as importantly, what it keeps apart:

example score
siblings Rajesh Kumar Sharma / Ramesh Kumar Sharma 0.886
gender variants Nandan Kumar / Nandini Kumar 0.763
shared common surname Amit Singh / Rahul Singh 0.273
initial collisions S Kumar / Sunita Kumar 0.653

The algorithms

Seven, across five families, chosen so their failure modes do not overlap. rarity_weighted is the one NameMatcher uses; the rest are exposed for comparison and research.

matcher family what it adds
exact equality floor, for reference
levenshtein character edits OCR substitutions
jaro_winkler character + prefix typos, shared prefixes
token_set token algebra reordering, dropped names, initials
phonetic sound transliteration (Soundex + syllable guard)
hybrid composite best evidence per token
rarity_weighted composite + rarity the above, weighted by token rarity
from indic_namematch import REGISTRY
REGISTRY["phonetic"]("Lakshmi Narayanan", "Laxmi Narayanan")   # 0.975

Benchmark

benchmark/ holds 120 hand-labelled pairs across 21 categories, 60 matches and 60 non-matches, with a harness that runs every algorithm and reports a per-category breakdown plus every individual error.

python3 benchmark/evaluate.py

Notably, 18 pairs are marked as not resolvable from the strings alone, and for 17 of them that is provable: each has a structural twin elsewhere in the set with the same shape and the opposite label, so any rule that gets one right must get the other wrong. They set an explicit ceiling on what any string algorithm can achieve. See benchmark/DATASET.md.

Design notes

  • Matchers return a score, never a decision. A threshold is a business parameter.
  • score(a, b) == score(b, a) is an enforced contract, tested on every pair. Neither document in a KYC pair is authoritative and you do not control which arrives first.
  • Soundex alone maps sunil and sunila to the same code, which merges men and women. The syllable guard fixes that without costing anything on real transliteration variants.
  • Name matching alone cannot close identity. It is one signal beside PAN, date of birth and face match, and it should be allowed to abstain.

Contributing

New benchmark rows are the most valuable contribution, particularly ones that some reasonable algorithm gets wrong. See CONTRIBUTING.md.

License

MIT. See LICENSE.

Metadata

Release files for indic-namematch 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for indic-namematch 0.1.0
File Size Uploaded
indic_namematch-0.1.0.tar.gz 55.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for indic-namematch 0.1.0
File Interpreter ABI Platform
indic_namematch-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 80.0 kB

Release files / indic_namematch-0.1.0.tar.gz

Download URL indic_namematch-0.1.0.tar.gz
Size 55.4 kB
Tags Source
SHA-256 checksum
How to use checksums
2351daf74dec07d68bc2d5c5b30b5e9b63ec08e9b6e54c54fc134317fe4cbf68
BLAKE2b-256 checksum
How to use checksums
e2c9717ad3c9274656ae1f43c37868c3395b64969b5b2edf8646d6b7774eb6f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.8

Release files / indic_namematch-0.1.0-py3-none-any.whl

Download URL indic_namematch-0.1.0-py3-none-any.whl
Size 24.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2adae0195dcee8960989aed7ca2ef4d8b8e61b6ebf72b240f6fdc9aaccf061a5
BLAKE2b-256 checksum
How to use checksums
2e4c58ec74f3edecbfb07e36fd6af1ea6f038fd20dc6effe6b1a4a44617ae88c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.8

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page