Move SSD detect log fields to typed Kotlin

ober

abaab6e346a91f7e9e4d4b521ef78b1962074a80

diff --git a/.build.yml b/.build.yml
index 8666805..8ebb922 100644
--- a/.build.yml
+++ b/.build.yml
@@ -5,7 +5,7 @@ packages:
   - make=4.4.1-r4
 sources:
   # Build dependency: full immutable commit, mirrored in dependencies.lock.json.
-  - "https://git.sr.ht/~lisp/jerboa#95eb94930e82dc21c387b51cd97081a239c98931"
+  - "https://git.sr.ht/~lisp/jerboa#1668be75bfbde97ecbf729e038c656cba6d3ffab"
   # The second source is the build subject selected by the SourceHut submitter.
   - https://git.sr.ht/~lisp/jerboa-android
 tasks:
@@ -14,6 +14,6 @@ tasks:
       test "$(apk info -v chez-scheme)" = chez-scheme-10.3.0-r2
       test "$(apk info -v git)" = git-2.54.0-r0
       test "$(apk info -v make)" = make-4.4.1-r4
-      test "$(git -C ../jerboa rev-parse HEAD)" = 95eb94930e82dc21c387b51cd97081a239c98931
-      test "$(git -C ../jerboa rev-parse 'HEAD^{tree}')" = 3d2c0da525a711c886d8e2372daf89240d01f96f
+      test "$(git -C ../jerboa rev-parse HEAD)" = 1668be75bfbde97ecbf729e038c656cba6d3ffab
+      test "$(git -C ../jerboa rev-parse 'HEAD^{tree}')" = 75d8ab23e37c03002c2ca593ef3920f8857a9ca8
       JERBOA="chez --libdirs .:../jerboa/lib --script" make test
diff --git a/dependencies.lock.json b/dependencies.lock.json
index bc500f2..168756a 100644
--- a/dependencies.lock.json
+++ b/dependencies.lock.json
@@ -11,8 +11,8 @@
   "generator_runtime": {
     "name": "jerboa",
     "repository": "https://git.sr.ht/~lisp/jerboa",
-    "commit": "95eb94930e82dc21c387b51cd97081a239c98931",
-    "tree": "3d2c0da525a711c886d8e2372daf89240d01f96f"
+    "commit": "1668be75bfbde97ecbf729e038c656cba6d3ffab",
+    "tree": "75d8ab23e37c03002c2ca593ef3920f8857a9ca8"
   },
   "assurance_tools": {
     "osv_scanner": {
diff --git a/scripts/verify-supply-chain.sh b/scripts/verify-supply-chain.sh
index 808c76a..040b4ad 100755
--- a/scripts/verify-supply-chain.sh
+++ b/scripts/verify-supply-chain.sh
@@ -3,8 +3,8 @@ set -eu
 
 repo=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P)
 lock="$repo/dependencies.lock.json"
-jerboa_commit=95eb94930e82dc21c387b51cd97081a239c98931 # gitsafe:ignore
-jerboa_tree=3d2c0da525a711c886d8e2372daf89240d01f96f # gitsafe:ignore
+jerboa_commit=1668be75bfbde97ecbf729e038c656cba6d3ffab # gitsafe:ignore
+jerboa_tree=75d8ab23e37c03002c2ca593ef3920f8857a9ca8 # gitsafe:ignore
 gradle_sha=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
 jdk_macos_sha=8fa1eff40bb637a33613b2ccb8b12c70dc3661cc22cf8e784943715769a05336
 jdk_linux_sha=d8afc263758141a66e0e3aafc321e783f7016696f4eaea067d340a269037d331
diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 61c8477..4f1dab5 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -3408,6 +3408,42 @@
                     "."
                     (string-append (int->string tenths) " s")))))))))
 
+    (typed-kotlin-file "com/sfb/ssdreview/DetectLogFields.kt"
+      (kotlin-imports (org json JSONObject))
+      (typed-library (com sfb ssdreview)
+        (export detectResultLogFields)
+        (type JSONObject)
+        (type Int32)
+        (type String)
+        (def (detectResultLogFields (stage : String)
+                                    (session : SsdSession)
+                                    (detectorSource : String)
+                                    (learnedGuesses : Int32)
+                                    (ocrGuesses : Int32)
+                                    (nativeAvailable : Bool)
+                                    (nativeError : String)) : JSONObject
+          (let ((out (json-object-empty)))
+            (begin
+              (json-object-put-string! out "stage" stage)
+              (json-object-put-string! out "detector_source" detectorSource)
+              (json-object-put-string! out "source_key" (SsdSession-sourceKey session))
+              (json-object-put-string! out "source_name" (SsdSession-sourceName session))
+              (json-object-put-int32! out "page" (SsdSession-page session))
+              (json-object-put-int32! out "dpi" (SsdSession-dpi session))
+              (json-object-put-int32! out "image_width" (SsdSession-imageWidth session))
+              (json-object-put-int32! out "image_height" (SsdSession-imageHeight session))
+              (json-object-put-int32! out "cells" (list-size (SsdSession-cells session)))
+              (json-object-put-int32! out "groups" (list-size (SsdSession-groups session)))
+              (json-object-put-int32! out "suppressed_groups"
+                (list-size (SsdSession-suppressedGroups session)))
+              (json-object-put-int32! out "ocr_words" (list-size (SsdSession-ocrWords session)))
+              (json-object-put-int32! out "ocr_guesses" ocrGuesses)
+              (json-object-put-int32! out "learned_guesses" learnedGuesses)
+              (json-object-put-bool! out "ssd_area" (not (nullable-null? (SsdSession-ssdArea session))))
+              (json-object-put-bool! out "native_available" nativeAvailable)
+              (json-object-put-string! out "native_error" nativeError)
+              out)))))
+
     (typed-kotlin-file "com/sfb/ssdreview/TruthIndexSelect.kt"
       (typed-library (com sfb ssdreview)
         (export bestTruthIndexForSession)
@@ -5382,24 +5418,18 @@
        "        learnedGuesses: Int = 0,"
        "        ocrGuesses: Int = 0"
        "    ) {"
-       "        logClientEvent(\"ssd-detect-result\", JSONObject()"
-       "            .put(\"stage\", stage)"
-       "            .put(\"detector_source\", detectorSource)"
-       "            .put(\"source_key\", session.sourceKey)"
-       "            .put(\"source_name\", session.sourceName)"
-       "            .put(\"page\", session.page)"
-       "            .put(\"dpi\", session.dpi)"
-       "            .put(\"image_width\", session.imageWidth)"
-       "            .put(\"image_height\", session.imageHeight)"
-       "            .put(\"cells\", session.cells.size)"
-       "            .put(\"groups\", session.groups.size)"
-       "            .put(\"suppressed_groups\", session.suppressedGroups.size)"
-       "            .put(\"ocr_words\", session.ocrWords.size)"
-       "            .put(\"ocr_guesses\", ocrGuesses)"
-       "            .put(\"learned_guesses\", learnedGuesses)"
-       "            .put(\"ssd_area\", session.ssdArea != null)"
-       "            .put(\"native_available\", SsdVisionBridge.isAvailable())"
-       "            .put(\"native_error\", SsdVisionBridge.loadErrorMessage()))"
+       "        logClientEvent("
+       "            \"ssd-detect-result\","
+       "            detectResultLogFields("
+       "                stage,"
+       "                session,"
+       "                detectorSource,"
+       "                learnedGuesses,"
+       "                ocrGuesses,"
+       "                SsdVisionBridge.isAvailable(),"
+       "                SsdVisionBridge.loadErrorMessage()"
+       "            )"
+       "        )"
        "    }"
        ""
        "    private fun sendClientLog(payload: JSONObject, readTimeoutMillis: Int) {"