@trillboards/edge-sdk 0.2.106 → 0.2.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,7 +20,18 @@
20
20
  "its scores agree to 7.2e-08, but row-wise the two disagree and lexsorting by box does",
21
21
  "not collapse it. The model returns ZERO detections above 0.25 on 40 real production",
22
22
  "frames, so there is no above-threshold sample to compare and row-level parity is",
23
- "UNVERIFIED. It is not published. Settle it on a corpus the model actually fires on."
23
+ "UNVERIFIED. It is not published. Settle it on a corpus the model actually fires on.",
24
+ "",
25
+ "2026-08-26: the 22nd twin, gh_fall_tonlong_v2, is the first whose LiteRT input is",
26
+ "NHWC [1,640,640,3]. The line above saying an ultralytics detect export is NOT NHWC",
27
+ "described the TOOLCHAIN the first 21 were exported with, not LiteRT: onnx2tf",
28
+ "transposes to channels-last by design and ultralytics 8.3.145 leaves it that way.",
29
+ "verify_litert_twin_parity.py used to ASSERT NCHW and refused a correct twin; it now",
30
+ "reads the input layout off the graph -- the same rule it already applied to outputs",
31
+ "-- and feeds each runtime the SAME IMAGE in the layout that runtime declares. The",
32
+ "warning that motivated the assertion still stands: transposing because 'LiteRT is",
33
+ "channels-last' feeds a rotated image to a model that wanted NCHW, and every residual",
34
+ "then reads as export noise."
24
35
  ],
25
36
  "measured_at": "2026-08-25",
26
37
  "tolerances": {
@@ -308,6 +319,21 @@
308
319
  "reason": "ok",
309
320
  "onnx": "weapon_gunknife_11n.onnx",
310
321
  "litert": "weapon_gunknife_11n.tflite"
322
+ },
323
+ {
324
+ "layout": "anchor_4_plus_nc",
325
+ "imgsz": 640,
326
+ "box_scale_applied": 640.0,
327
+ "box_residual_px": 0.0007171630859375,
328
+ "box_residual_unscaled": 646.9068156480789,
329
+ "cls_residual": 1.3665612286217765e-07,
330
+ "pass": true,
331
+ "reason": "ok",
332
+ "onnx": "gh_fall_tonlong_v2.onnx",
333
+ "litert": "gh_fall_tonlong_v2.tflite",
334
+ "litert_input_layout": "nhwc",
335
+ "measured_at": "2026-08-26",
336
+ "note": "The 22nd twin, added 2026-08-26, and the FIRST whose LiteRT input is NHWC. gh_fall_tonlong had no parity measurement at all until now: upstream ships `Violence` at slot 0 and `violence` at slot 6, normalize_class_name folds them to one name and ModelVocabulary refuses at construction, so neither format could be probed. Both were re-exported from ok.pt (sha256 1e12d89198e28d7c650ba86886c511fecd9f70b9d7f8bf985e76704690e58dd9) with the slots disambiguated by INDEX -- violence_slot0 / violence_slot6 -- which preserves upstream truth and asserts no semantics we have not measured."
311
337
  }
312
338
  ]
313
339
  }
@@ -0,0 +1,371 @@
1
+ #!/usr/bin/env python3
2
+ """Re-apply the venue adjudication's own bar with the control-selection fix.
3
+
4
+ WHY THIS EXISTS
5
+ ---------------
6
+ `venue_adjudication.py` chose the control a verdict rests on with
7
+ `argmax(rate_025)` over every set listed for the class. That is a selector with no
8
+ access to the contact-sheet grade, so it reliably picks the set that flatters the
9
+ model. On 2026-08-26 it picked `fire_generic` -- n=5, graded `good_small_n`, whose
10
+ own note reads "n=5 so only a coarse liveness signal" -- over `fire_cctv`, n=39,
11
+ graded `good`, "realistic scale for a venue camera", scored in the same run against
12
+ the same artifact at the same threshold. `fire` and `smoke` were awarded PUBLISH on
13
+ the 5-frame set.
14
+
15
+ The tool is fixed. Re-running it end to end needs the per-frame score matrices
16
+ (`<alias>.npz` + `combo.json`) from the eval box, which are not committed. This
17
+ re-applies the SAME bar to the numbers the artifact already carries, so the
18
+ corrected verdict is derivable from what is in the repo -- and, crucially, marks
19
+ each capability `proven` or `needs_remeasure` rather than pretending the elided
20
+ rows are known.
21
+
22
+ WHAT IT CANNOT DO, AND SAYS SO
23
+ ------------------------------
24
+ `venue_adjudication.py` elides `per_control` for candidates ranked 5+ that carry no
25
+ shipped bytes. For those rows only the WINNING set's rate survives. Where the
26
+ winning set was liveness-only, that candidate's rateable-control rate is unknown
27
+ and the capability is reported `needs_remeasure` with the candidate named. An
28
+ unknown is not a pass and it is not a fail; it is the reason to re-run.
29
+
30
+ python3 regrade_control_selection.py \
31
+ --adjudication trillboard-api/scripts/ai/eval-baselines/venue-adjudication-2026-08-26.json \
32
+ --control-quality trillboard-api/scripts/ai/eval-baselines/positive-control-quality-2026-08-25.json \
33
+ --out trillboard-api/scripts/ai/eval-baselines/venue-adjudication-control-regrade-2026-08-26.json
34
+ """
35
+ from __future__ import annotations
36
+
37
+ import argparse
38
+ import json
39
+ import os
40
+
41
+ #: Grades whose own note says the set cannot rate a class. Kept in step with
42
+ #: `venue_adjudication.py::LIVENESS_ONLY_GRADES`; the test pins them equal.
43
+ LIVENESS_ONLY_GRADES = ("good_small_n", "weak")
44
+ UNUSABLE_GRADES = ("DISCARDED", "POOR", "CONTAMINATED")
45
+
46
+ #: What a capability becomes when the PUBLISH is provably withdrawn but what
47
+ #: replaces it is not. NOT a verdict a model may ship on, and NOT a failure: the
48
+ #: elided rows could still clear the bar, and writing "NO MODEL CLEARS BAR" over
49
+ #: them would be the same mistake in the other direction -- a definitive claim
50
+ #: standing on evidence that was never committed.
51
+ UNRESOLVED = "UNRESOLVED PENDING REMEASURE"
52
+
53
+ #: Priors that an unknown may hold open. A PUBLISH because it is exactly what this
54
+ #: tool exists to withdraw; `UNRESOLVED` because this tool writes it, and a run
55
+ #: over its own output must not harden "not known" into "known bad".
56
+ PRESERVE_ON_BLOCKING = ("PUBLISH", UNRESOLVED)
57
+
58
+
59
+ def rateable_controls(per_control: dict, ctlq: dict) -> dict:
60
+ """The controls that may carry a verdict: graded, usable, not liveness-only."""
61
+ out = {}
62
+ for name, row in (per_control or {}).items():
63
+ grade = (ctlq.get(name) or {}).get("grade")
64
+ if grade in LIVENESS_ONLY_GRADES or grade in UNUSABLE_GRADES:
65
+ continue
66
+ out[name] = dict(row, grade=grade)
67
+ return out
68
+
69
+
70
+ def regrade_capability(cap: dict, ctlq: dict, bar: dict) -> dict:
71
+ """Re-apply the bar to one capability using only rateable controls."""
72
+ recall_bar = bar["recall_retention"]
73
+ off_bar = bar["off_target_mean"]
74
+ prior = cap["decision"].get("decision")
75
+
76
+ # `decide()` resolves two branches BEFORE it ever looks at a control, and both
77
+ # are properties of the negative set. Re-deriving them here would only
78
+ # reproduce them from less information, and getting them subtly wrong is how a
79
+ # regrade starts reporting movement it did not cause -- the first run of this
80
+ # tool "moved" eight capabilities that have no control involved at all.
81
+ if not cap["candidates"] or prior in ("NO CANDIDATE", "NOT SCREENABLE HERE"):
82
+ return {
83
+ "capability": cap["capability"],
84
+ "prior_decision": prior,
85
+ "regraded_decision": prior,
86
+ "changed": False,
87
+ "status": "not_affected",
88
+ "why": "resolved before any control is read: %s" % (prior,),
89
+ }
90
+
91
+ rows, unknown = [], []
92
+ for cand in cap["candidates"]:
93
+ elided = "_elided" in (cand.get("by_bucket") or {})
94
+ rate = rateable_controls(cand.get("per_control"), ctlq)
95
+ if not rate:
96
+ # No per-control detail survived the elision. If the set this row was
97
+ # decided on is itself rateable, the surviving number IS the rateable
98
+ # number and the row is knowable; otherwise it is not.
99
+ best = cand.get("best_control")
100
+ bg = (ctlq.get(best) or {}).get("grade") if best else None
101
+ if best and bg not in LIVENESS_ONLY_GRADES and bg not in UNUSABLE_GRADES:
102
+ rows.append({
103
+ "model": cand["model"], "class": cand["class"],
104
+ "control": best, "control_n": cand.get("control_n"),
105
+ "grade": bg,
106
+ "rate_at_quiet_t": cand.get("control_rate_at_quiet_t"),
107
+ "rate_025": cand.get("control_rate_025"),
108
+ "off_target_mean": cand.get("off_target_mean"),
109
+ "quiet_t": cand.get("quiet_t"),
110
+ "n_valid_negatives": cand.get("n_valid_negatives"),
111
+ "source": "winning set was already rateable",
112
+ })
113
+ else:
114
+ unknown.append({
115
+ "model": cand["model"], "class": cand["class"],
116
+ "decided_on": best, "grade": bg,
117
+ "why_unknown": ("per_control elided for this rank and the set it was "
118
+ "decided on cannot rate a class, so its rateable-control "
119
+ "rate was never committed"),
120
+ "elided": elided,
121
+ })
122
+ continue
123
+ # argmax(rate_025) among the rateable, exactly as the fixed tool does.
124
+ name = max(rate, key=lambda k: (rate[k]["rate_025"] or 0))
125
+ row = rate[name]
126
+ rows.append({
127
+ "model": cand["model"], "class": cand["class"],
128
+ "control": name, "control_n": row["n"], "grade": row["grade"],
129
+ "rate_at_quiet_t": row["rate_at_quiet_t"], "rate_025": row["rate_025"],
130
+ "off_target_mean": cand.get("off_target_mean"),
131
+ "quiet_t": cand.get("quiet_t"),
132
+ "n_valid_negatives": cand.get("n_valid_negatives"),
133
+ "also_scored": {k: {"n": v["n"], "grade": (ctlq.get(k) or {}).get("grade"),
134
+ "rate_at_quiet_t": v["rate_at_quiet_t"]}
135
+ for k, v in (cand.get("per_control") or {}).items() if k != name},
136
+ "source": "recomputed from committed per_control",
137
+ })
138
+
139
+ live = [r for r in rows if (r["rate_025"] or 0) > 0]
140
+ retained = [r for r in live if (r["rate_at_quiet_t"] or 0) >= recall_bar]
141
+ ok = [r for r in retained
142
+ if r["off_target_mean"] is not None and r["off_target_mean"] <= off_bar]
143
+
144
+ if ok:
145
+ # The artifact's own ordering is preserved: candidates arrive sorted.
146
+ win = ok[0]
147
+ verdict = "PUBLISH"
148
+ why = ("clears the bar on %s (n=%d, grade %s): keeps %.1f%% at conf %s"
149
+ % (win["control"], win["control_n"], win["grade"],
150
+ 100 * (win["rate_at_quiet_t"] or 0), win["quiet_t"]))
151
+ elif live:
152
+ best = max(live, key=lambda r: (r["rate_at_quiet_t"] or 0))
153
+ verdict = "NO MODEL CLEARS BAR"
154
+ why = ("no candidate keeps >=%.0f%% of a control that can rate a class. Best is "
155
+ "%s / '%s' at %.1f%% of %s (n=%d, grade %s)."
156
+ % (100 * recall_bar, best["model"], best["class"],
157
+ 100 * (best["rate_at_quiet_t"] or 0), best["control"],
158
+ best["control_n"], best["grade"]))
159
+ win = None
160
+ else:
161
+ verdict, why, win = "UNINFORMATIVE", "no candidate fires on a rateable control", None
162
+
163
+ # An unknown row can only ever ADD a passing candidate, so it can overturn a
164
+ # failing verdict and can never overturn a PUBLISH.
165
+ blocking = unknown if verdict != "PUBLISH" else []
166
+
167
+ # ...and a verdict an unknown could overturn must not be stated as if it could
168
+ # not. Withdrawing the PUBLISH is CERTAIN -- it rested on a set that cannot
169
+ # rate a class, and no re-measurement changes that. Replacing it with a
170
+ # definitive failure is NOT certain, because an elided candidate could still
171
+ # clear the bar. Two different claims; only the first is proved here, and
172
+ # asserting the second would be the same error in the other direction.
173
+ #
174
+ # Gated on the PRIOR. Where the prior was already a MEASURED failure, the
175
+ # original run reached it with MORE controls and better data than this tool
176
+ # has for the elided rows, so overwriting it with `UNRESOLVED` would be this
177
+ # tool asserting its own blindness over a measurement. Those rows still carry
178
+ # `status: needs_remeasure`; their verdict is left alone.
179
+ #
180
+ # `UNRESOLVED` is in the set because THIS TOOL WRITES IT. Without it, running
181
+ # `--apply` twice would see `prior == UNRESOLVED`, skip this branch, and
182
+ # overwrite its own careful "not known" with a definitive `NO MODEL CLEARS
183
+ # BAR` -- the exact claim the first run refused to make, on the same
184
+ # evidence. The guard would not catch it either: it audits PUBLISH rows only.
185
+ # `--selftest` pins the idempotence.
186
+ if blocking and verdict == "NO MODEL CLEARS BAR" and prior in PRESERVE_ON_BLOCKING:
187
+ why = ("the prior verdict is WITHDRAWN -- it rested on a control that cannot rate a "
188
+ "class, which no re-measurement changes. What REPLACES it is not yet known: "
189
+ + why
190
+ + (" %d candidate(s) had their rateable-control rate elided and could still "
191
+ "clear the bar (%s). Re-run venue_adjudication.py against the score "
192
+ "matrices to settle it."
193
+ % (len(blocking), ", ".join("%s/%s" % (b["model"], b["class"])
194
+ for b in blocking))))
195
+ verdict = UNRESOLVED
196
+
197
+ return {
198
+ "capability": cap["capability"],
199
+ "prior_decision": prior,
200
+ "prior_control": cap["decision"].get("control"),
201
+ "prior_control_n": cap["decision"].get("control_n"),
202
+ "prior_control_grade": (ctlq.get(cap["decision"].get("control")) or {}).get("grade"),
203
+ "prior_events_lost": (cap["decision"].get("gate") or {}).get("events_lost"),
204
+ "regraded_decision": verdict,
205
+ "changed": bool(prior and prior != verdict),
206
+ "status": "needs_remeasure" if blocking else "proven",
207
+ "why": why,
208
+ "winner": win,
209
+ "candidates_regraded": len(rows),
210
+ "candidates_unknown": blocking,
211
+ }
212
+
213
+
214
+ def _selftest() -> None:
215
+ """Pin the two things a reader cannot see by reading the ladder.
216
+
217
+ 1. IDEMPOTENCE. `--apply` writes `UNRESOLVED` into the artifact it reads. A
218
+ second run must reproduce it, not harden it into a failure.
219
+ 2. A MEASURED failure is left alone. A prior that was already
220
+ `NO MODEL CLEARS BAR` came from a run with more controls than this tool
221
+ holds for an elided row, and must not be softened to `UNRESOLVED` either.
222
+ """
223
+ ctlq = {"big": {"grade": "good"}, "tiny": {"grade": "good_small_n"}}
224
+ bar = {"recall_retention": 0.5, "off_target_mean": 0.1}
225
+
226
+ def cap(prior):
227
+ return {
228
+ "capability": "fire",
229
+ "decision": {"decision": prior, "control": "tiny", "control_n": 5},
230
+ "candidates": [
231
+ { # a candidate that fails on the rateable set
232
+ "model": "m1", "class": "fire", "quiet_t": 0.25,
233
+ "off_target_mean": 0.0, "n_valid_negatives": 100,
234
+ "by_bucket": {}, "best_control": "big", "control_n": 9,
235
+ "control_rate_025": 0.4, "control_rate_at_quiet_t": 0.4,
236
+ "per_control": {"big": {"n": 9, "rate_025": 0.4, "rate_at_quiet_t": 0.4},
237
+ "tiny": {"n": 5, "rate_025": 0.9, "rate_at_quiet_t": 0.9}},
238
+ },
239
+ { # ...and one whose rateable rate was elided
240
+ "model": "m2", "class": "fire", "quiet_t": 0.25,
241
+ "off_target_mean": 0.0, "n_valid_negatives": 100,
242
+ "by_bucket": {"_elided": "summary row"},
243
+ "best_control": "tiny", "control_n": 5,
244
+ "control_rate_025": 0.9, "control_rate_at_quiet_t": 0.9,
245
+ "per_control": {},
246
+ },
247
+ ],
248
+ }
249
+
250
+ first = regrade_capability(cap("PUBLISH"), ctlq, bar)
251
+ assert first["regraded_decision"] == UNRESOLVED, first["regraded_decision"]
252
+ assert first["candidates_unknown"], "the elided row must be reported"
253
+
254
+ second = regrade_capability(cap(first["regraded_decision"]), ctlq, bar)
255
+ assert second["regraded_decision"] == UNRESOLVED, (
256
+ "re-running over this tool's own output hardened an unknown into %r"
257
+ % second["regraded_decision"])
258
+ assert second["changed"] is False, "a settled UNRESOLVED must not read as changed"
259
+
260
+ measured = regrade_capability(cap("NO MODEL CLEARS BAR"), ctlq, bar)
261
+ assert measured["regraded_decision"] == "NO MODEL CLEARS BAR", measured["regraded_decision"]
262
+
263
+ print("selftest OK: idempotent on UNRESOLVED; a measured failure is left alone")
264
+
265
+
266
+ def main() -> None:
267
+ ap = argparse.ArgumentParser()
268
+ if "--selftest" in __import__("sys").argv[1:]:
269
+ _selftest()
270
+ return
271
+ ap.add_argument("--adjudication", required=True)
272
+ ap.add_argument("--control-quality", required=True)
273
+ ap.add_argument("--out", required=True)
274
+ ap.add_argument("--apply", action="store_true",
275
+ help="also write the corrected verdict back into --adjudication. The "
276
+ "adjudication is the record; leaving a verdict in it that its own "
277
+ "numbers refute is a second source of truth, not a safer one.")
278
+ args = ap.parse_args()
279
+
280
+ adj = json.load(open(args.adjudication))
281
+ ctlq = json.load(open(args.control_quality))
282
+ bar = adj["bar"]
283
+
284
+ rows = [regrade_capability(c, ctlq, bar) for c in adj["capabilities"]]
285
+ changed = [r for r in rows if r["changed"]]
286
+
287
+ # The regrade artifact is a record OF A TRANSITION. Once `--apply` has run, the
288
+ # adjudication no longer holds the verdict that moved, so a second run reads
289
+ # nothing to change and would write an empty `changed_list` over the record of
290
+ # what did. The adjudication itself is idempotent (proved by `--selftest` and
291
+ # by re-running `--apply`, which leaves it byte-identical); this file is not,
292
+ # and erasing it silently would leave the corrected artifact with nothing
293
+ # beside it saying what it was corrected FROM.
294
+ if not changed and os.path.exists(args.out):
295
+ try:
296
+ prev = json.load(open(args.out))
297
+ except (OSError, ValueError):
298
+ prev = {}
299
+ if (prev.get("summary") or {}).get("changed"):
300
+ raise SystemExit(
301
+ "refusing to overwrite %s: it records %d capability transition(s) and this "
302
+ "run found none, because the adjudication it read has ALREADY been corrected. "
303
+ "That is the expected result of re-running, not a reason to erase the record. "
304
+ "Point --out somewhere else, or re-run against an uncorrected adjudication."
305
+ % (args.out, (prev["summary"] or {})["changed"]))
306
+
307
+ json.dump({
308
+ "schema": "trillboards.venue-adjudication-control-regrade/1",
309
+ "regrades": adj["schema"] + " @ " + adj["negative_set"],
310
+ "_what": "the SAME bar, re-applied with the control-selection fix: a verdict may "
311
+ "only rest on a control whose contact-sheet grade can rate a class.",
312
+ "_not_a_re_run": "the per-frame scores are unchanged and are NOT re-derived here. "
313
+ "This re-reads the numbers the adjudication already committed. A "
314
+ "capability marked `needs_remeasure` has a candidate whose "
315
+ "rateable-control rate was elided and must come from a re-run.",
316
+ "liveness_only_grades": list(LIVENESS_ONLY_GRADES),
317
+ "unusable_grades": list(UNUSABLE_GRADES),
318
+ "bar": bar,
319
+ "summary": {
320
+ "capabilities": len(rows),
321
+ "changed": len(changed),
322
+ "changed_list": [{"capability": r["capability"],
323
+ "was": r["prior_decision"], "now": r["regraded_decision"],
324
+ "status": r["status"]} for r in changed],
325
+ },
326
+ "capabilities": rows,
327
+ }, open(args.out, "w"), indent=1)
328
+
329
+ if args.apply and changed:
330
+ by_cap = {r["capability"]: r for r in changed}
331
+ for cap in adj["capabilities"]:
332
+ r = by_cap.get(cap["capability"])
333
+ if not r:
334
+ continue
335
+ was = dict(cap["decision"])
336
+ cap["decision"] = {
337
+ "decision": r["regraded_decision"],
338
+ "why": r["why"],
339
+ # The gate is a promise computed from a recall this verdict no
340
+ # longer accepts. Keeping it would leave an events-lost figure in
341
+ # the file with nothing behind it.
342
+ "gate": None,
343
+ "corrected": {
344
+ "on": "2026-08-26",
345
+ "by": "deploy/detector/bench/regrade_control_selection.py --apply",
346
+ "was": was,
347
+ "ships": False,
348
+ "why_corrected": (
349
+ "the verdict rested on `%s` (n=%s, grade %s), a set whose own "
350
+ "contact-sheet note says it can only show liveness. Re-applying "
351
+ "this run's OWN bar to the controls that can rate a class "
352
+ "overturns it." % (was.get("control"), was.get("control_n"),
353
+ r.get("prior_control_grade"))),
354
+ "needs_remeasure": r["candidates_unknown"],
355
+ "regrade_artifact": args.out,
356
+ },
357
+ }
358
+ cap["changed"] = bool(cap.get("prior_decision_taxi_negatives")
359
+ and cap["prior_decision_taxi_negatives"]
360
+ != r["regraded_decision"])
361
+ json.dump(adj, open(args.adjudication, "w"), indent=1)
362
+
363
+ print(json.dumps({"capabilities": len(rows), "changed": len(changed),
364
+ "applied": bool(args.apply and changed),
365
+ "changed_list": [(r["capability"], r["prior_decision"],
366
+ r["regraded_decision"], r["status"])
367
+ for r in changed]}, indent=1))
368
+
369
+
370
+ if __name__ == "__main__":
371
+ main()