refactor(car): drop dead FuzzyNodeNameResolver duplicate (#5994)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
James Rich
2026-06-28 09:54:18 -05:00
committed by GitHub
parent b913d9550f
commit a048ea1c94
2 changed files with 0 additions and 151 deletions

View File

@@ -1,73 +0,0 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.feature.car.util
import org.koin.core.annotation.Factory
/**
* Resolves voice-spoken node names to actual node numbers using fuzzy matching.
*
* TODO: Consolidate with FuzzyNameResolver from core/data when AppFunctions branch merges.
*/
@Factory
class FuzzyNodeNameResolver {
data class ResolvedNode(val nodeNum: Int, val name: String, val confidence: Float)
fun resolve(spokenName: String, nodes: List<Pair<Int, String>>): ResolvedNode? {
if (spokenName.isBlank() || nodes.isEmpty()) return null
val normalizedInput = spokenName.lowercase().trim()
return nodes
.map { (nodeNum, name) ->
val normalizedName = name.lowercase().trim()
val score = lcsScore(normalizedInput, normalizedName)
ResolvedNode(nodeNum, name, score)
}
.filter { it.confidence >= MIN_CONFIDENCE }
.maxByOrNull { it.confidence }
}
private fun lcsScore(a: String, b: String): Float {
if (a.isEmpty() || b.isEmpty()) return 0f
val maxLen = maxOf(a.length, b.length)
val lcsLen = lcsLength(a, b)
return lcsLen.toFloat() / maxLen.toFloat()
}
private fun lcsLength(a: String, b: String): Int {
val m = a.length
val n = b.length
val dp = Array(m + 1) { IntArray(n + 1) }
for (i in 1..m) {
for (j in 1..n) {
dp[i][j] =
if (a[i - 1] == b[j - 1]) {
dp[i - 1][j - 1] + 1
} else {
maxOf(dp[i - 1][j], dp[i][j - 1])
}
}
}
return dp[m][n]
}
companion object {
private const val MIN_CONFIDENCE = 0.6f
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.feature.car.util
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
class FuzzyNodeNameResolverTest {
private val resolver = FuzzyNodeNameResolver()
private val testNodes =
listOf(1 to "Alice Base Station", 2 to "Bob Mobile", 3 to "Charlie Repeater", 4 to "Delta Gateway")
@Test
fun `resolve returns exact match with high confidence`() {
val result = resolver.resolve("Alice Base Station", testNodes)
assertNotNull(result)
assertEquals(1, result.nodeNum)
assertEquals(1f, result.confidence)
}
@Test
fun `resolve handles case-insensitive matching`() {
val result = resolver.resolve("alice base station", testNodes)
assertNotNull(result)
assertEquals(1, result.nodeNum)
}
@Test
fun `resolve returns partial match with sufficient confidence`() {
val result = resolver.resolve("Alice Base Staton", testNodes)
assertNotNull(result)
assertEquals(1, result.nodeNum)
assertTrue(result.confidence >= 0.6f)
}
@Test
fun `resolve returns null for blank input`() {
assertNull(resolver.resolve("", testNodes))
assertNull(resolver.resolve(" ", testNodes))
}
@Test
fun `resolve returns null for empty node list`() {
assertNull(resolver.resolve("Alice", emptyList()))
}
@Test
fun `resolve returns null for low-confidence match`() {
assertNull(resolver.resolve("zzz", testNodes))
}
@Test
fun `resolve picks best match among similar names`() {
val nodes = listOf(1 to "Charlie Alpha", 2 to "Charlie Bravo")
val result = resolver.resolve("Charlie Bravo", nodes)
assertNotNull(result)
assertEquals(2, result.nodeNum)
}
}