0075 double metaphone takes doublemetaphone as its oracle - CyrilB1531/lodestar GitHub Wiki
0075 โ Double Metaphone ships, with doublemetaphone 1.2 as its oracle
Status: accepted ยท Date: 2026-09-02
Context
#314 exists because a survey claim turned
out to be false. jellyfish 1.2.1 โ this repository's oracle for the other encoders โ exports
damerau_levenshtein_distance hamming_distance jaccard_similarity jaro_similarity
jaro_winkler_similarity levenshtein_distance match_rating_codex match_rating_comparison
metaphone nysiis soundex
and no Double Metaphone. It was in older releases and is not in this one, so "jellyfish has it and we do not" was never the gap.
That turns an implementation lot into a decision. Reproducing Double Metaphone means adding a
development dependency to tools/generate_oracles.py, and every oracle library here is pinned by
hash: widening that surface changes how conformance is proven, not just what is convenient.
Decision 0036 offers a third way โ
ship without an oracle and say so โ which had to be beaten rather than ignored.
Read and measured on 2026-09-02. Every figure below is what the named artefact produced that day.
The fear, and the measurement that removes it
Double Metaphone has no normative specification, only Lawrence Philips' original C++ and a crowd of ports. The real risk was therefore not "which library" but "which dialect" โ if the candidates disagree, then picking one is picking a behaviour, and the corpus would freeze an accident.
So they were compared against each other, over the 401 distinct words already in
tests/oracles/phonetics.json โ the repository's own phonetics corpus, real names and
generated strings both, rather than a word list chosen for this question.
One convention has to be normalised first: where a word has no alternate encoding, metaphone
and phonetics return '' for the secondary while doublemetaphone repeats the primary. That is
presentation, not phonetics.
| comparison | primary code | both codes, normalised |
|---|---|---|
metaphone 0.6 vs doublemetaphone 1.2 |
0 / 401 | 16 / 401 |
phonetics 1.0.5 vs doublemetaphone 1.2 |
3 / 401 | 4 / 401 |
metaphone 0.6 vs phonetics 1.0.5 |
3 / 401 | 20 / 401 |
The dialect fear does not survive. Two independently written permissive implementations produce the same primary code on all 401 words, and the remaining differences are defects with a side rather than disagreements about the algorithm:
metaphone0.6 emits a trailing space in the secondary code of 16 words โdoublemetaphone("Cj")is('KJ', 'K')wheremetaphonegives('KJ', 'K '). Strip whitespace and the two agree on 401 / 401, both codes.phonetics1.0.5 drops characters.DmtpbfโTMTPwhere both others giveTMTPF;lnvppqfvโLNFPFFagainstLNFPKFF;qappjvgโKPFKagainstKPJFK. It also diverges on one secondary,wpbczgtrโPTSKTRagainstPXKTR.
A library that loses a character from a phonetic key is not a candidate for freezing behaviour.
The candidates, and why the field narrows to one
| package | version | licence | last upload |
|---|---|---|---|
doublemetaphone |
1.2 | Artistic 2.0 | 2025-05-11 |
abydos |
0.5.0 | GPLv3+ | 2020-01-11 |
metaphone |
0.6 | BSD | 2016-08-24 |
fuzzy |
1.2.2 | Artistic / MIT | 2017-10-16 |
phonetics |
1.0.5 | MIT | 2018-03-23 |
abydos is excluded on licence. ADR 0003 refuses copyleft
even for generating test data โ "neither transcribed nor even used to generate test data (for
hygiene, though the GPL claims nothing over a program's outputs)" โ and that rule is what removed
python-Levenshtein. It applies here unchanged.
doublemetaphone 1.2 carries the Artistic License 2.0, verified from the LICENSE file in
its own wheel rather than from the PyPI classifier, which says only "Artistic License". 2.0 is a
free, GPL-compatible, non-copyleft licence โ it is not the vague 1.0, and ADR 0003's hygiene rule
does not reach it. The posture is the one rapidfuzz and jellyfish already have: a development
dependency, run to produce JSON, never redistributed and never transcribed.
The remaining three are permissive and agree well enough to be usable, so the tiebreaker is
maintenance. doublemetaphone shipped 2025-05-11; the newest of the others is 2018. A
frozen corpus outlives the session that made it, and an oracle nobody has touched in eight years
is one nobody will fix.
It is also installable under this repository's own constraint: CI installs with
--only-binary :all: --require-hashes, and 1.2 publishes 89 wheels including
cp312-manylinux_2_17_x86_64 โ the interpreter and platform the oracle job runs on.
Decision
Double Metaphone ships, and doublemetaphone 1.2 is its oracle, pinned by hash in
tools/requirements.lock.txt like every other oracle library.
The encoder then follows lot 1's shape โ the stemmer and phonetics lots' shape โ with the gates
#313 names: a frozen corpus compared exactly
for strings, an entry in docs/equivalence.md in the same commit as the function, a reference page
under docs/reference/text/phonetics/, and a member reference from samples/Lodestar.Sample.
The secondary code is normalised on the way in. The corpus stores '' for "no alternate",
which is the convention jellyfish, metaphone and phonetics share and the one the C# API will
expose; doublemetaphone's repeated primary is unwrapped by the generator. That is a presentation
choice made once, in one place, and recorded here so the corpus is not read as disagreeing with
its own source.
What was rejected
Decision 0036's third option โ ship without an oracle, say so, and name what would retire the exception. It is the right instrument when no reference exists, and that was the premise here until the comparison ran. Two permissive implementations agreeing on 401 of 401 primaries is a reference; declining to use it would be spending 0036's exception on a case that does not need it, and 0036 is worth less every time it is spent loosely.
metaphone 0.6, on the strength of agreeing with the winner. It does agree โ exactly, once
its trailing space is stripped โ but that strip would be a workaround in the generator for a
defect upstream, and the package has not shipped since 2016.
Dropping Double Metaphone and pointing callers at docs/migration/. It is the encoder people
ask for by name, and the reason to drop it had been "no oracle exists". One does.
Consequences
tools/requirements.lock.txtgainsdoublemetaphone==1.2with its hashes, andTHIRD-PARTY-NOTICES.mdgains its attribution โ ADR 0003 requires the notice to move at the same time as the dependency.- The oracle surface grows by one library. That is the cost this decision is really about, and the measurement above is what pays for it.
- The corpus records
doublemetaphone1.2 in its metadata, so a future divergence is traceable to a version rather than to "the reference". - If a later release of
jellyfishrestores Double Metaphone, this decision is worth revisiting: one fewer library would be strictly better, and the 401-word comparison above is the test that would say whether the two agree.