Move SSD client log payload to typed Kotlin

ober

911c2ce60c5dc78d5ee0aa720d7e60372173c053

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index ad9575a..6ebdb59 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -3509,6 +3509,48 @@
               (json-object-put-json-object! out "truth" truth)
               out)))))
 
+    (typed-kotlin-file "com/sfb/ssdreview/ClientLogJson.kt"
+      (kotlin-imports (org json JSONObject))
+      (typed-library (com sfb ssdreview)
+        (export clientLogPayloadJson)
+        (type JSONObject)
+        (type Int)
+        (type Int32)
+        (type String)
+        (def (clientLogPdfJson (pdfName : String)
+                               (page : Int32)
+                               (pageCount : Int32)) : JSONObject
+          (let ((out (json-object-empty)))
+            (begin
+              (json-object-put-string! out "name" pdfName)
+              (json-object-put-int32! out "page" page)
+              (json-object-put-int32! out "pages" pageCount)
+              out)))
+        (def (clientLogPayloadJson (event : String)
+                                   (uptimeMs : Int)
+                                   (threadName : String)
+                                   (app : JSONObject)
+                                   (device : JSONObject)
+                                   (pdfName : String)
+                                   (page : Int32)
+                                   (pageCount : Int32)
+                                   (selectedGroupId : String)
+                                   (fields : JSONObject)) : JSONObject
+          (let ((out (json-object-empty)))
+            (begin
+              (json-object-put-int32! out "schema" (int32 1))
+              (json-object-put-string! out "event" event)
+              (json-object-put-int! out "client-time-ms" (system-current-time-millis))
+              (json-object-put-int! out "uptime-ms" uptimeMs)
+              (json-object-put-string! out "thread" threadName)
+              (json-object-put-json-object! out "app" app)
+              (json-object-put-json-object! out "device" device)
+              (json-object-put-json-object! out "pdf"
+                (clientLogPdfJson pdfName page pageCount))
+              (json-object-put-string! out "selected-group" selectedGroupId)
+              (json-object-put-json-object! out "fields" fields)
+              out)))))
+
     (typed-kotlin-file "com/sfb/ssdreview/TruthIndexSelect.kt"
       (typed-library (com sfb ssdreview)
         (export bestTruthIndexForSession)
@@ -5518,17 +5560,18 @@
        "    }"
        ""
        "    private fun clientLogPayload(event: String, fields: JSONObject): JSONObject ="
-       "        JSONObject()"
-       "            .put(\"schema\", 1)"
-       "            .put(\"event\", event)"
-       "            .put(\"client-time-ms\", System.currentTimeMillis())"
-       "            .put(\"uptime-ms\", SystemClock.uptimeMillis())"
-       "            .put(\"thread\", Thread.currentThread().name ?: \"\")"
-       "            .put(\"app\", appLogInfo())"
-       "            .put(\"device\", deviceLogInfo())"
-       "            .put(\"pdf\", JSONObject().put(\"name\", pdfName).put(\"page\", pageIndex + 1).put(\"pages\", pageCount))"
-       "            .put(\"selected-group\", selectedGroupId ?: \"\")"
-       "            .put(\"fields\", fields)"
+       "        clientLogPayloadJson("
+       "            event,"
+       "            SystemClock.uptimeMillis(),"
+       "            Thread.currentThread().name ?: \"\","
+       "            appLogInfo(),"
+       "            deviceLogInfo(),"
+       "            pdfName,"
+       "            pageIndex + 1,"
+       "            pageCount,"
+       "            selectedGroupId ?: \"\","
+       "            fields"
+       "        )"
        ""
        "    private fun appLogInfo(): JSONObject {"
        "        val out = JSONObject().put(\"package\", packageName)"