- distance.ts: hand-rolled 3-band OSA, adjacent transposition = 1 edit, zero imports (D-07); the row rotation is pinned by the D1-D9 vectors - score.ts: TIER bands strictly disjoint descending — EXACT 1.00 / ALIAS_EXACT 0.90 / PREFIX 0.75 / TOKEN_SET 0.65 / FUZZY as the 0.60 CEILING (a fuzzy score is fuzzySim * 0.60, never a 0.00 multiplier); FUZZY_ADMIT_SIMILARITY = 0.72 exported for source.ts + the corpus runner; token-level residual lets 'Sentrl' reach 'KL Sentral' while 'Jln Tun Razak' cannot reach 'Jalan Ampang' - scoreCandidate returns the RAW matched alias (corpus provenance); an exact ALIAS hit rides ALIAS_EXACT so a name-exact outranks it - score.test.mjs S7 corrected during GREEN: admission belongs to source.ts — scoreCandidate asserts the sub-floor similarity instead D1-D9 + S1-S7 green; typecheck clean.
70 lines
2.6 KiB
JavaScript
70 lines
2.6 KiB
JavaScript
#!/usr/bin/env node
|
||
/*
|
||
* distance.test.mjs — hand-computed Damerau-Levenshtein (OSA) vectors (SRCH-02).
|
||
* ---------------------------------------------------------------------------
|
||
* RED phase (plan 04-01): every case fails while ../src/distance.ts does not
|
||
* exist — the import happens lazily inside each case, so D1–D9 report one
|
||
* FAIL per vector instead of a lost suite load.
|
||
*
|
||
* The row-rotation in a 3-band OSA is easy to get wrong — these vectors are
|
||
* written FIRST and made to pass second (the repo RED->GREEN convention).
|
||
*
|
||
* Run: npm run search:test (exit 0 = all green)
|
||
*/
|
||
import { strict as assert } from "node:assert";
|
||
import { test } from "./harness.mjs";
|
||
|
||
// Lazy distance load — missing module => per-case ERR_MODULE_NOT_FOUND (the RED state).
|
||
let distancePromise = null;
|
||
function loadDamerau() {
|
||
if (!distancePromise) {
|
||
distancePromise = import("../src/distance.ts").then((m) => m.damerau);
|
||
}
|
||
return distancePromise;
|
||
}
|
||
|
||
test("D1 adjacent transposition — damerau('ab','ba') === 1", async () => {
|
||
const damerau = await loadDamerau();
|
||
assert.equal(damerau("ab", "ba"), 1, "a swap of adjacent chars is ONE edit in OSA");
|
||
});
|
||
|
||
test("D2 two transpositions — damerau('a cat','an act') === 2", async () => {
|
||
const damerau = await loadDamerau();
|
||
assert.equal(damerau("a cat", "an act"), 2, "insert n, then transpose 'ca' -> 'ac'");
|
||
});
|
||
|
||
test("D3 empty/empty — damerau('','') === 0", async () => {
|
||
const damerau = await loadDamerau();
|
||
assert.equal(damerau("", ""), 0);
|
||
});
|
||
|
||
test("D4 empty target — damerau('','abc') === 3", async () => {
|
||
const damerau = await loadDamerau();
|
||
assert.equal(damerau("", "abc"), 3, "three insertions");
|
||
});
|
||
|
||
test("D5 empty source — damerau('abc','') === 3", async () => {
|
||
const damerau = await loadDamerau();
|
||
assert.equal(damerau("abc", ""), 3, "three deletions");
|
||
});
|
||
|
||
test("D6 identity — damerau('same','same') === 0", async () => {
|
||
const damerau = await loadDamerau();
|
||
assert.equal(damerau("same", "same"), 0);
|
||
});
|
||
|
||
test("D7 corpus typo transposition — damerau('klcc','kclc') === 1", async () => {
|
||
const damerau = await loadDamerau();
|
||
assert.equal(damerau("klcc", "kclc"), 1, "KCLC must reach KLCC as ONE edit (D-13 typo bucket)");
|
||
});
|
||
|
||
test("D8 corpus typo transposition — damerau('sunway','sunwya') === 1", async () => {
|
||
const damerau = await loadDamerau();
|
||
assert.equal(damerau("sunway", "sunwya"), 1, "SUNWYA must reach SUNWAY as ONE edit");
|
||
});
|
||
|
||
test("D9 classic Levenshtein sanity — damerau('kitten','sitting') === 3", async () => {
|
||
const damerau = await loadDamerau();
|
||
assert.equal(damerau("kitten", "sitting"), 3, "sub k->s, sub e->i, append g");
|
||
});
|