Ethiopian/Eritrean name intelligence: parsing, transliteration, spelling variants, and patronymic-aware fuzzy matching.
Project description
habesha-names
Ethiopian/Eritrean name intelligence for Python: fidel script handling, transliteration, spelling-variant generation, name parsing, and patronymic-aware fuzzy matching.
Status
Current release: 0.1.0. The linguistic defaults (practical transliteration rules) and the bundled name lexicon are native-speaker verified (see Data verification). The golden test corpus is mechanically generated and partially curated. The public API is stable within the 0.1.x series.
- Zero runtime dependencies — stdlib only
- Deterministic and explainable — no ML at runtime, no network calls; every match score ships an explanation object. Built for KYC/AML, remittance, HR, and entity-resolution pipelines.
- Fully typed (
py.typed, mypy strict)
Why
Habesha names break global identity systems:
- No family names. A full name is given name + father's given name (+ grandfather's). "First/Last name" fields are semantically wrong.
- No standard romanization. ጸሐይ → Tsehay / Tsehai / Sehay / Tzehay — same person, four database records.
- Compound given names. "Haile Mariam" can be ONE given name (Hailemariam) or given + patronym.
- Abbreviation conventions. Gebremedhin → G/Medhin, G.Medhin, Gebre Medhin — all common in official documents.
- Fidel homophones. ሀ/ሐ/ኀ, ሰ/ሠ, ጸ/ፀ, አ/ዐ are pronounced identically; spelling varies by writer.
Install
pip install habesha-names
(From a checkout: pip install -e .)
Quick tour
Every snippet below is a doctest and runs in CI.
Parse — name structure, not first/last fields
>>> from habesha_names import parse
>>> p = parse("ወይዘሮ ጸሐይ ገብረመድህን")
>>> (p.title, p.given, p.patronym)
('Weizero', 'ጸሀይ', 'ገብረመድህን')
>>> p.script
'ethiopic'
>>> parse("Hailemariam Desalegn").given_is_compound
True
>>> parse("G/Medhin Tesfaye").given # slash abbreviation expanded
'Gebremedhin'
>>> parse("Bikila, Abebe").given # comma inversion handled
'Abebe'
Variants — the spellings your database actually contains
>>> from habesha_names import variants
>>> variants("ጸሐይ", n=6)
['Tsehay', 'Sehay', 'Tsehai', 'Tzehay', 'Tsehaye', 'Sehai']
>>> variants("Gebremedhin", n=5)
['Gebremedhin', 'Gebre Medhin', 'Gebre-Medhin', 'G/Medhin', 'G.Medhin']
Match — patronymic-aware fuzzy matching
>>> from habesha_names import match
>>> match("Ato Abebe Bikila", "abebe bikila") >= 0.85
True
>>> round(float(match("Tesfay Mohamed", "Tesfaye Muhammed")), 2)
0.94
>>> match("Abebe Bikila", "Bikila Abebe").swapped # field swap tolerated
True
>>> match("Abebe Bikila", "Almaz Tesfahun") <= 0.6
True
Score interpretation
Match scores are calibrated to three bands:
| score | reading |
|---|---|
| ≥ 0.85 | likely the same person |
| 0.60 – 0.85 | review zone — route to an analyst |
| ≤ 0.60 | likely different people |
The middle band is intentional, not indecision: records like "Tesfaye Girma" vs "Tesfahun Girma" (siblings — different given name, shared patronym) score there by design, because in KYC/AML pipelines a shared patronym is exactly the kind of near-match a human should see.
Explainability — every score can be justified
>>> result = match("ወይዘሮ ጸሐይ ገብረመድህን", "Tsehay G/Medhin")
>>> result.score
1.0
>>> [(pair.token_a, pair.token_b, pair.method) for pair in result.pairs]
[('ጸሀይ', 'Tsehay', 'exact'), ('ገብረመድህን', 'Gebremedhin', 'exact')]
>>> for note in result.notes:
... print(note)
a: patronym 'ገብረመድህን' is a joined compound (Gebre + Medhin)
b: abbreviation 'G/Medhin' expanded with top candidate 'Gebre' (candidates: Gebre (0.8), Girma (0.2))
Normalize — fidel homophones collapse before comparison
>>> from habesha_names import normalize
>>> normalize("ፀሐይ") # ፀ→ጸ, ሐ→ሀ
'ጸሀይ'
>>> normalize("ፀሐይ") == normalize("ጸሀይ")
True
Transliterate — practical romanization, no diacritics
>>> from habesha_names import transliterate
>>> transliterate("ተስፋዬ")
'Tesfaye'
>>> transliterate("ገብረመድህን")
'Gebremedhin'
>>> transliterate("ፀሐይ") == transliterate("ጸሀይ") == "Tsehay"
True
Building blocks
>>> from habesha_names import is_ethiopic, phonetic_key
>>> is_ethiopic("ተስፋዬ")
True
>>> phonetic_key("Tsehay") == phonetic_key("Sehai") # HabeshaKey
True
Public API
from habesha_names import (
parse, match, variants, transliterate,
normalize, phonetic_key, is_ethiopic,
)
Everything else is internal. Reverse transliteration (to_fidel) and
gender inference (guess_gender) are planned for v0.2.
Known limitations
- Bekele ↔ Bikila score 0.90 — the phonetic key's single first-vowel
class slot folds these two distinct names together. A richer vowel
representation is planned for v0.2; until then this pair is a recorded
known_failin the golden corpus. - Spelling rewrites inside spaced compound forms can misalign against
the joined form (e.g. "Gebrie Medhin" vs "Gebremedhin"), also recorded
as
known_failcorpus entries.
Data verification
The bundled lexicons (given names, titles, compound elements) and the
practical transliteration rules passed native-speaker review in July
2026 and are flagged "verified": true; any newly added entry starts
false again until reviewed. The golden test corpus remains
mechanically generated (needs_human markers) pending human curation,
and tuning constants are accepted for 0.1.0 as-is — to be revisited
against a human-curated corpus.
Development
python -m venv .venv
.venv\Scripts\activate.bat
pip install -e .[dev]
check.bat # pytest -q && ruff check . && mypy src --strict
See ARCHITECTURE.md for the design and CHANGELOG.md for release history.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file habesha_names-0.1.0.tar.gz.
File metadata
- Download URL: habesha_names-0.1.0.tar.gz
- Upload date:
- Size: 82.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
123e63aceca1547658ae65e903a08b89439ebb69c65601cf2f7149cd57501d77
|
|
| MD5 |
ac5eb23c9da1f19dfca30afdd883ba7f
|
|
| BLAKE2b-256 |
907db04b7aa95c0273098e895118d14e0bb00427e4e4b328b7a27da9ca0cc808
|
Provenance
The following attestation bundles were made for habesha_names-0.1.0.tar.gz:
Publisher:
release.yml on Robel231/habesha-names
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
habesha_names-0.1.0.tar.gz -
Subject digest:
123e63aceca1547658ae65e903a08b89439ebb69c65601cf2f7149cd57501d77 - Sigstore transparency entry: 2168976711
- Sigstore integration time:
-
Permalink:
Robel231/habesha-names@ade16758aa78c8c84999e1b1102b575fe3993df3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Robel231
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ade16758aa78c8c84999e1b1102b575fe3993df3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file habesha_names-0.1.0-py3-none-any.whl.
File metadata
- Download URL: habesha_names-0.1.0-py3-none-any.whl
- Upload date:
- Size: 52.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
610ab2ef06e4b9dea9c02e099e09215a7f9c4cd78237475a68b3834d24758071
|
|
| MD5 |
3e0cff0259f6fd2537f9595f03ea71e5
|
|
| BLAKE2b-256 |
2a1c7928ff41e08cba2c08209130ae12018f3d8a64aec51b8f7187d5e76b098d
|
Provenance
The following attestation bundles were made for habesha_names-0.1.0-py3-none-any.whl:
Publisher:
release.yml on Robel231/habesha-names
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
habesha_names-0.1.0-py3-none-any.whl -
Subject digest:
610ab2ef06e4b9dea9c02e099e09215a7f9c4cd78237475a68b3834d24758071 - Sigstore transparency entry: 2168976731
- Sigstore integration time:
-
Permalink:
Robel231/habesha-names@ade16758aa78c8c84999e1b1102b575fe3993df3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Robel231
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ade16758aa78c8c84999e1b1102b575fe3993df3 -
Trigger Event:
push
-
Statement type: