Move SSD OCR token matching to typed Kotlin

ober

38e0b257e675bcc94d24e973464e9eddeeb7a4c2

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index d604e9f..9bc1a1a 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -366,7 +366,8 @@
 
     (typed-kotlin-file "com/sfb/ssdreview/OcrStrings.kt"
       (typed-library (com sfb ssdreview)
-        (export normalizeOcr compact bankCompatible suppressedByRace shouldReplaceOcrLabel)
+        (export normalizeOcr compact ocrTokenMatches
+                bankCompatible suppressedByRace shouldReplaceOcrLabel)
         (def (normalizeOcr (value : String)) : String
           (string-replace-regex
             (string-trim
@@ -378,6 +379,15 @@
             " "))
         (def (compact (value : String)) : String
           (string-filter-letter-or-digit (string-lowercase value)))
+        (def (ocrTokenMatches (text : String) (token : String)) : Bool
+          (let ((normalized (normalizeOcr text))
+                (compactText (compact text))
+                (tokenNormal (normalizeOcr token))
+                (tokenCompact (compact token)))
+            (or (string-contains?
+                  (string-append " " (string-append normalized " "))
+                  (string-append " " (string-append tokenNormal " ")))
+                (string-contains? compactText tokenCompact))))
         (def (bankCompatible (label : String) (boxTypeId : String)) : Bool
           (let ((key (compact label)))
             (or (or (or (equal? boxTypeId "26")
@@ -3196,7 +3206,7 @@
        "        val seen = mutableSetOf<String>()"
        "        val candidates = mutableListOf<OcrCandidate>()"
        "        patterns.forEach { pattern ->"
-       "            if (!anyTokenMatch(text, pattern.tokens)) return@forEach"
+       "            if (!pattern.tokens.any { token -> ocrTokenMatches(text, token) }) return@forEach"
        "            if (suppressedByRace(race, pattern.label)) return@forEach"
        "            val resolved = BoxTypes.resolve(context, pattern.label)"
        "            val label = resolved?.name ?: pattern.label"
@@ -3208,16 +3218,6 @@
        "        return candidates.sortedByDescending { it.confidence }"
        "    }"
        ""
-       "    private fun anyTokenMatch(text: String, tokens: List<String>): Boolean {"
-       "        val normalized = normalizeOcr(text)"
-       "        val compactText = compact(text)"
-       "        return tokens.any { token ->"
-       "            val tokenNormal = normalizeOcr(token)"
-       "            val tokenCompact = compact(token)"
-       "            \" $normalized \".contains(\" $tokenNormal \") || compactText.contains(tokenCompact)"
-       "        }"
-       "    }"
-       ""
        "}"
        ))
     (kotlin-file-lines "com/sfb/ssdreview/SsdReviewView.kt"