From 2127072a5828caea9efe78692877c5b68a30ba0c Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Wed, 28 Jan 2026 09:49:24 -0600 Subject: [PATCH] change(web): add .edgeKey for use + validation of complex .split cases Build-bot: skip build Test-bot: skip --- .../main/correction/search-quotient-spur.ts | 21 ++++++- .../search-quotient-spur.tests.ts | 61 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts index 7a69c2bd99d..d32cfa8becb 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts @@ -476,4 +476,23 @@ export abstract class SearchQuotientSpur implements SearchQuotientNode { return components.join('+'); } -} \ No newline at end of file + + /** + * Provides a unique hash-style string corresponding to the edge represented + * by this graph spur. + * + * This is designed for use in two cases: + * 1. At runtime - plays a role in simplification of results for .split + * operations with SearchQuotientCluster ancestry, which may cause + * quotient-paths that diverge and reconverge during recursion + * - Specifically, this property assists detection of duplicate instances + * during 'diverged' states, allowing reconvergence to be handled. + * 2. In unit tests - validating that splits with SearchQuotientCluster + * ancestry properly handle quotient-path variance in unit tests. + */ + get edgeKey(): string { + const inputSrc = this.inputSource; + const segment = inputSrc.segment; + return `E${inputSrc.subsetId}:${segment.start}${segment.end !== undefined ? `-${segment.end}` : ''}`; + } +} diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/search-quotient-spur.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/search-quotient-spur.tests.ts index b026f7eb9bf..a8a32fa8b4f 100644 --- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/search-quotient-spur.tests.ts +++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/search-quotient-spur.tests.ts @@ -322,6 +322,67 @@ describe('SearchQuotientSpur', () => { }); }); + describe('.edgeKey', () => { + it('changes when input source subset IDs differ', () => { + const root = new LegacyQuotientRoot(testModel); + + const {distributions} = buildSimplePathSplitFixture(); + const inputSrc = { + segment: { + transitionId: distributions[0][0].sample.id, + start: 0 + }, + subsetId: generateSubsetId(), + bestProbFromSet: distributions[0][0].p + }; + + const spur1 = new LegacyQuotientSpur(root, distributions[0], { + ...inputSrc, + subsetId: generateSubsetId() + }); + const spur2 = new LegacyQuotientSpur(root, distributions[0], { + ...inputSrc, + subsetId: generateSubsetId() + }); + + assert.notEqual(spur1.edgeKey, spur2.edgeKey); + }); + + it('changes when different parts of the same input source are used', () => { + const root = new LegacyQuotientRoot(testModel); + + const {distributions} = buildSimplePathSplitFixture(); + const inputSrc = { + segment: { + transitionId: distributions[0][0].sample.id, + start: 0 + }, + subsetId: generateSubsetId(), + bestProbFromSet: distributions[0][0].p + }; + + const spur1 = new LegacyQuotientSpur(root, distributions[0], inputSrc); + const spur2 = new LegacyQuotientSpur(root, distributions[0], { + ...inputSrc, + segment: { + ...inputSrc.segment, + end: 1 + } + }); + const spur3 = new LegacyQuotientSpur(root, distributions[0], { + ...inputSrc, + segment: { + ...inputSrc.segment, + start: inputSrc.segment.start + 1 + } + }); + + assert.notEqual(spur1.edgeKey, spur2.edgeKey); + assert.notEqual(spur2.edgeKey, spur3.edgeKey); + assert.notEqual(spur3.edgeKey, spur1.edgeKey); + }); + }); + describe('split()', () => { describe(`on token comprised of single-char transforms: [crt][ae][nr][t]`, () => { const runSplit = (splitIndex: number) => {