Move SSD learned suppression to typed Kotlin

ober

9983117125ace1f522cbbfd77fa1a4bbc94a671f

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 5c8d082..a15803e 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -1327,6 +1327,31 @@
                   (and (equal? status "candidate")
                        (string-starts-with? notes "Guess:")))))))))
 
+    (typed-kotlin-file "com/sfb/ssdreview/LearnedSuppression.kt"
+      (kotlin-imports (org json JSONObject))
+      (typed-library (com sfb ssdreview)
+        (export suppressedLearnedExample suppressedLearnedForGroup)
+        (type JSONObject)
+        (def (suppressedLearnedExample (session : SsdSession) (example : JSONObject)) : Bool
+          (let ((label (json-object-opt-string example "label"))
+                (boxTypeId (json-object-opt-string example "box_type_id"))
+                (race
+                  (raceKey
+                    (string-append
+                      (SsdSession-sourceName session)
+                      (string-append " " (SsdSession-sourceUri session))))))
+            (if (equal? race "andromedan")
+                (isShieldLabel label)
+                (isPaPanelExample label boxTypeId))))
+        (def (suppressedLearnedForGroup (group : SsdGroup) (example : JSONObject)) : Bool
+          (if (bankCompatible (SsdGroup-label group) (SsdGroup-boxTypeId group))
+              (if (bankCompatible
+                    (json-object-opt-string example "label")
+                    (json-object-opt-string example "box_type_id"))
+                  #f
+                  #t)
+              #f))))
+
     (typed-kotlin-file "com/sfb/ssdreview/OcrGeometry.kt"
       (typed-library (com sfb ssdreview)
         (export centerInside rectArea normalizeRect rectsOverlap distanceToRect)
@@ -5915,19 +5940,6 @@
        "        return file.inputStream().use { readBoundedBytes(it, MAX_ZIP_ENTRY_BYTES).decodeToString() }"
        "    }"
        ""
-       "    private fun suppressedLearnedExample(session: SsdSession, example: JSONObject): Boolean {"
-       "        val label = example.optString(\"label\")"
-       "        val boxTypeId = example.optString(\"box_type_id\")"
-       "        val andromedan = raceKey(\"${session.sourceName} ${session.sourceUri}\") == \"andromedan\""
-       "        if (!andromedan && isPaPanelExample(label, boxTypeId)) return true"
-       "        if (andromedan && isShieldLabel(label)) return true"
-       "        return false"
-       "    }"
-       ""
-       "    private fun suppressedLearnedForGroup(group: SsdGroup, example: JSONObject): Boolean {"
-       "        if (!bankCompatible(group.label, group.boxTypeId)) return false"
-       "        return !bankCompatible(example.optString(\"label\"), example.optString(\"box_type_id\"))"
-       "    }"
        "}"
        ""
        "fun sha1Hex(input: InputStream): String {"