Move SSD source key helper to typed Kotlin
ober
466a7159196cd20239312952d748e5ecbd7ce78b
--- 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#c162fc3b392e2f055676421a89b3edc195f931e6" + - "https://git.sr.ht/~lisp/jerboa#42cdf9570d7e13fc7d728d7109e9465a1a5d16ba" # 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)" = c162fc3b392e2f055676421a89b3edc195f931e6 - test "$(git -C ../jerboa rev-parse 'HEAD^{tree}')" = 5a49d1c3b19e73575cec87bdc2d862fc943e4c78 + test "$(git -C ../jerboa rev-parse HEAD)" = 42cdf9570d7e13fc7d728d7109e9465a1a5d16ba + test "$(git -C ../jerboa rev-parse 'HEAD^{tree}')" = 4b8530cc73186492d38a7cf1ce6d7b4834c02d01 JERBOA="chez --libdirs .:../jerboa/lib --script" make test --- a/dependencies.lock.json +++ b/dependencies.lock.json @@ -11,8 +11,8 @@ "generator_runtime": { "name": "jerboa", "repository": "https://git.sr.ht/~lisp/jerboa", - "commit": "c162fc3b392e2f055676421a89b3edc195f931e6", - "tree": "5a49d1c3b19e73575cec87bdc2d862fc943e4c78" + "commit": "42cdf9570d7e13fc7d728d7109e9465a1a5d16ba", + "tree": "4b8530cc73186492d38a7cf1ce6d7b4834c02d01" }, "assurance_tools": { "osv_scanner": { --- a/full-kotlin.md +++ b/full-kotlin.md @@ -43,7 +43,7 @@ The vendored Jerboa tree already has the right starting point: Do not fork that model inside `jandroid.ss`. General Kotlin backend work belongs in Jerboa. Android project and Android DSL work belongs in `jerboa-android`. -Version caveat: this repository now pins Jerboa commit `c162fc3b392e2f055676421a89b3edc195f931e6`, tree `5a49d1c3b19e73575cec87bdc2d862fc943e4c78`. Before editing compiler APIs, check out the exact pinned commit (or the intended replacement commit), rerun the typed parser/checker/core inventory, and record any differences. Do not assume two Jerboa snapshots expose identical IR. +Version caveat: this repository now pins Jerboa commit `42cdf9570d7e13fc7d728d7109e9465a1a5d16ba`, tree `4b8530cc73186492d38a7cf1ce6d7b4834c02d01`. Before editing compiler APIs, check out the exact pinned commit (or the intended replacement commit), rerun the typed parser/checker/core inventory, and record any differences. Do not assume two Jerboa snapshots expose identical IR. ### 2.3 `~/sfb` source requirements --- 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=c162fc3b392e2f055676421a89b3edc195f931e6 # gitsafe:ignore -jerboa_tree=5a49d1c3b19e73575cec87bdc2d862fc943e4c78 # gitsafe:ignore +jerboa_commit=42cdf9570d7e13fc7d728d7109e9465a1a5d16ba # gitsafe:ignore +jerboa_tree=4b8530cc73186492d38a7cf1ce6d7b4834c02d01 # gitsafe:ignore gradle_sha=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78 jdk_macos_sha=8fa1eff40bb637a33613b2ccb8b12c70dc3661cc22cf8e784943715769a05336 jdk_linux_sha=d8afc263758141a66e0e3aafc321e783f7016696f4eaea067d340a269037d331 --- a/templates/ssd-review.ss +++ b/templates/ssd-review.ss @@ -187,6 +187,19 @@ (suppressedGroups : (MutableList SsdGroup)) (mut ssdArea : (Nullable FloatArray)))))) + (typed-kotlin-file "com/sfb/ssdreview/SourceKey.kt" + (typed-library (com sfb ssdreview) + (export sourceKey) + (type Int32) + (def (sourceKey (sourceSha1 : String) (page : Int32) (dpi : Int32)) : String + (string-append + (string-take sourceSha1 (int32 16)) + (string-append + "-p" + (string-append + (string-pad-start (int32->string page) (int32 4) #\0) + (string-append "-d" (int32->string dpi)))))))) + (typed-kotlin-file "com/sfb/ssdreview/NativeDetection.kt" (typed-library (com sfb ssdreview) (export make-NativeDetection NativeDetection? @@ -497,6 +510,17 @@ " }" " }" " if (count <= 4 && width * height < imageWidth * imageHeight * 0.006f) {" + " if (nearEdge && count >= 2) {" + " return if (race == \"andromedan\") {" + " Guess(\"pa panel\", \"PA Panel\", \"small Andromedan outer-edge bank geometry\")" + " } else {" + " Guess(" + " shieldLabel(cx / imageWidth.coerceAtLeast(1), cy / imageHeight.coerceAtLeast(1))," + " \"Shield\"," + " \"small outer-edge bank geometry\"" + " )" + " }" + " }" " return Guess(if (race == \"andromedan\") \"phaser-2\" else \"phaser\", \"Phaser\", \"compact weapon-size group\")" " }" " if (count in 1..3 && cy < imageHeight * 0.34f && cx > imageWidth * 0.35f && cx < imageWidth * 0.65f) {" @@ -832,6 +856,7 @@ " private fun shouldReplaceLabelForBoxType(label: String, previousBoxType: String): Boolean {" " val trimmed = label.trim()" " if (trimmed.isBlank() || isGenericGuessLabel(trimmed)) return true" + " if (trimmed.endsWith(\"?\")) return true" " return labelMatchesBoxType(trimmed, previousBoxType)" " }" "" @@ -863,6 +888,14 @@ " private fun normalizeLabel(value: String): String =" " value.lowercase().removeSuffix(\"?\").filter { it.isLetterOrDigit() }" "" + " private fun finalizeGroupLabel(group: SsdGroup) {" + " val typeDisplay = BoxTypes.load(this).firstOrNull { it.id == group.boxTypeId }?.let { box_type_display(it) } ?: group.boxTypeId" + " val resolved = resolvedLabelForBoxType(group.label, typeDisplay, group.boxTypeId)" + " if (resolved.isNotBlank()) {" + " group.label = resolved" + " }" + " }" + "" " private fun focusBoxTypeDropdown(dialog: AlertDialog, boxType: AutoCompleteTextView) {" " boxType.dropDownHeight = (resources.displayMetrics.heightPixels / 3).coerceAtMost(620)" " dialog.window?.setSoftInputMode(" @@ -1132,6 +1165,7 @@ " private fun approveSelectedGroup() {" " val session = currentSession ?: return" " val group = selectedGroup() ?: return" + " finalizeGroupLabel(group)" " group.status = \"reviewed\"" " group.notes = \"Approved on Android\"" " ssdSessionRecomputeGroup(session, group)" @@ -1555,7 +1589,9 @@ " val label = EditText(this).apply {" " hint = \"Label\"" " inputType = InputType.TYPE_CLASS_TEXT" - " setText(group.label)" + " val existing = BoxTypes.load(this@MainActivity).firstOrNull { it.id == group.boxTypeId }" + " val typeDisplay = existing?.let { box_type_display(it) } ?: group.boxTypeId" + " setText(resolvedLabelForBoxType(group.label, typeDisplay, group.boxTypeId))" " selectAll()" " }" " val boxType = boxTypeInput().apply {" @@ -3051,6 +3087,7 @@ " if (!shouldReplaceLabel(group)) return@forEach" " val text = nearbyText(session.ocrWords, group.bbox, session.imageWidth, session.imageHeight)" " val best = ocrCandidates(context, text, race).firstOrNull() ?: return@forEach" + " if (protectedBankLabel(group) && !bankCompatible(best.label, best.boxTypeId)) return@forEach" " group.label = \"${best.label}?\"" " if (best.boxTypeId.isNotBlank()) group.boxTypeId = best.boxTypeId" " group.notes = appendNote(group.notes, \"OCR: ${best.label}, ${best.reason}\")" @@ -3119,6 +3156,20 @@ " return group.status == \"candidate\" && group.notes.startsWith(\"Guess:\")" " }" "" + " private fun protectedBankLabel(group: SsdGroup): Boolean =" + " bankCompatible(group.label, group.boxTypeId)" + "" + " private fun bankCompatible(label: String, boxTypeId: String): Boolean {" + " val key = compact(label)" + " return boxTypeId == \"26\" ||" + " boxTypeId == \"52\" ||" + " boxTypeId == \"99\" ||" + " key.contains(\"shield\") ||" + " key.contains(\"papanel\") ||" + " key.contains(\"powerabsorber\") ||" + " key.contains(\"padegradation\")" + " }" + "" " private fun suppressedByRace(race: String, label: String): Boolean {" " val key = compact(label)" " if (race == \"andromedan\" && key.contains(\"shield\")) return true" @@ -4276,7 +4327,7 @@ " val example = examples.getJSONObject(i)" " if (example.optString(\"source_key\") == session.sourceKey) continue" " val label = example.optString(\"label\")" - " if (label.isBlank() || suppressedLearnedExample(session, example)) continue" + " if (label.isBlank() || suppressedLearnedExample(session, example) || suppressedLearnedForGroup(group, example)) continue" " val score = featureDistance(features, example.optJSONObject(\"features\") ?: continue)" " if (score < bestScore) {" " bestScore = score" @@ -4837,6 +4888,11 @@ " return false" " }" "" + " private fun suppressedLearnedForGroup(group: SsdGroup, example: JSONObject): Boolean {" + " if (!isBankLabel(group.label, group.boxTypeId)) return false" + " return !isBankLabel(example.optString(\"label\"), example.optString(\"box_type_id\"))" + " }" + "" " private fun isAndromedanSource(session: SsdSession): Boolean {" " val text = \"${session.sourceName} ${session.sourceUri}\".lowercase()" " return text.contains(\"andromedan\") || text.contains(\"module_c3\") || text.contains(\"module c3\")" @@ -4849,6 +4905,17 @@ " normalized.contains(\"papanel\") ||" " normalized.contains(\"padegradation\")" " }" + "" + " private fun isBankLabel(label: String, boxTypeId: String): Boolean {" + " val normalized = label.lowercase().filter { it.isLetterOrDigit() }" + " return boxTypeId == \"26\" ||" + " boxTypeId == \"52\" ||" + " boxTypeId == \"99\" ||" + " normalized.contains(\"shield\") ||" + " normalized.contains(\"papanel\") ||" + " normalized.contains(\"powerabsorber\") ||" + " normalized.contains(\"padegradation\")" + " }" "}" "" "fun sha1Hex(input: InputStream): String {" @@ -4862,7 +4929,5 @@ " return digest.digest().joinToString(\"\") { \"%02x\".format(it) }" "}" "" - "fun sourceKey(sourceSha1: String, page: Int, dpi: Int): String =" - " \"${sourceSha1.take(16)}-p%04d-d%d\".format(page, dpi)" )) ))