@trillboards/edge-sdk 0.2.80 → 0.2.81
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.
|
@@ -317,12 +317,40 @@ class TestClassFilter(ServerCase):
|
|
|
317
317
|
self.assertEqual(status, 400)
|
|
318
318
|
self.assertIn("shoplifter", body["error"])
|
|
319
319
|
|
|
320
|
-
def
|
|
320
|
+
def test_a_partly_unknown_list_is_ALSO_a_loud_400(self):
|
|
321
|
+
"""The partial case is the dangerous one, and it used to return 200.
|
|
322
|
+
|
|
323
|
+
`["person", "shoplifter"]` proceeded on `person` and dropped
|
|
324
|
+
`shoplifter` silently, so a profile asking about shoplifting got person
|
|
325
|
+
counts and no signal that its actual question was discarded. That is the
|
|
326
|
+
same `yolov8_nano` shape the test above is written against — a capability
|
|
327
|
+
offered and not delivered — only wearing a 200 instead of a 0%.
|
|
328
|
+
|
|
329
|
+
It becomes the COMMON case once specialist models exist: a PPE profile
|
|
330
|
+
asks for `glove` and `hairnet` alongside `person`, and on a box holding
|
|
331
|
+
only the general detector two thirds of the question would vanish.
|
|
332
|
+
|
|
333
|
+
Refusing costs the `person` detections too. That is the right trade —
|
|
334
|
+
which models are resident is decided upstream by the profile push, so a
|
|
335
|
+
class the box cannot answer is a configuration error, and the client maps
|
|
336
|
+
400 to `DetectorUnavailableError`, which shadow mode records as
|
|
337
|
+
`unavailable`. Visible beats quieter.
|
|
338
|
+
"""
|
|
339
|
+
loader = FakeLoader()
|
|
340
|
+
url = self.serve(loader)
|
|
341
|
+
status, body = self.detect(url, classes=["person", "shoplifter"])
|
|
342
|
+
self.assertEqual(status, 400)
|
|
343
|
+
self.assertIn("shoplifter", body["error"])
|
|
344
|
+
# And the detector was never asked — we refuse before inferring.
|
|
345
|
+
self.assertEqual(loader.calls, [])
|
|
346
|
+
|
|
347
|
+
def test_a_fully_known_list_still_reaches_the_detector(self):
|
|
348
|
+
"""The refusal above must not have made every class request fail."""
|
|
321
349
|
loader = FakeLoader()
|
|
322
350
|
url = self.serve(loader)
|
|
323
|
-
status, _ = self.detect(url, classes=["person", "
|
|
351
|
+
status, _ = self.detect(url, classes=["person", "cell phone"])
|
|
324
352
|
self.assertEqual(status, 200)
|
|
325
|
-
self.assertEqual(loader.calls[0][2], {"person"})
|
|
353
|
+
self.assertEqual(loader.calls[0][2], {"person", "cell phone"})
|
|
326
354
|
|
|
327
355
|
|
|
328
356
|
class TestRequestRefusals(ServerCase):
|
|
@@ -115,12 +115,35 @@ def parse_detect_request(raw: bytes) -> tuple[bytes, float, "set[str] | None"]:
|
|
|
115
115
|
raise WireError(400, "`classes` was an empty array; omit the key to mean every class")
|
|
116
116
|
|
|
117
117
|
wanted, unknown = resolve_requested_classes(raw_classes)
|
|
118
|
-
|
|
118
|
+
# ANY unknown class is a 400, not just all of them.
|
|
119
|
+
#
|
|
120
|
+
# This used to reject only when `wanted` came back empty, so a request for
|
|
121
|
+
# ["person", "glove"] against a COCO-only model returned person detections
|
|
122
|
+
# and dropped `glove` on the floor. `resolve_requested_classes` keeps its own
|
|
123
|
+
# promise — it returns the unknowns rather than discarding them — and its
|
|
124
|
+
# docstring says exactly why: "a profile naming a class the model cannot emit
|
|
125
|
+
# is exactly the `yolov8_nano` shape". The caller then discarded them one
|
|
126
|
+
# level up, which is where the promise was actually broken.
|
|
127
|
+
#
|
|
128
|
+
# The partial case is the dangerous one and it becomes the COMMON one the
|
|
129
|
+
# moment specialist models exist: a PPE profile asks for `glove` and
|
|
130
|
+
# `hairnet` alongside `person`, and on a box holding only the general
|
|
131
|
+
# detector the operator would get person counts and silence about gloves.
|
|
132
|
+
#
|
|
133
|
+
# Refusing loses the person detections too, which is the cost. It is the
|
|
134
|
+
# right trade: which models are resident is decided upstream by the profile
|
|
135
|
+
# push, so a class the box cannot answer is a configuration error, and the
|
|
136
|
+
# client maps a 400 to `DetectorUnavailableError` -> shadow mode records
|
|
137
|
+
# `unavailable`. A wrong configuration becomes visible instead of becoming a
|
|
138
|
+
# quieter answer.
|
|
139
|
+
if unknown:
|
|
119
140
|
raise WireError(
|
|
120
141
|
400,
|
|
121
|
-
f"
|
|
142
|
+
f"requested classes not in this model's vocabulary: {unknown}. "
|
|
122
143
|
f"Known classes: {sorted(KNOWN_CLASS_NAMES)}",
|
|
123
144
|
)
|
|
145
|
+
if not wanted:
|
|
146
|
+
raise WireError(400, "`classes` resolved to nothing requestable")
|
|
124
147
|
return jpeg, min_confidence, wanted
|
|
125
148
|
|
|
126
149
|
|
package/dist/EdgeAgent.js
CHANGED
|
@@ -44,7 +44,7 @@ const edge_ads_1 = require("@trillboards/edge-ads");
|
|
|
44
44
|
const CommandRouter_1 = require("./CommandRouter");
|
|
45
45
|
const config_1 = require("./config");
|
|
46
46
|
const dataDir_1 = require("./dataDir");
|
|
47
|
-
const SDK_VERSION = '0.2.
|
|
47
|
+
const SDK_VERSION = '0.2.81';
|
|
48
48
|
class EdgeAgent extends events_1.EventEmitter {
|
|
49
49
|
constructor(config, deps) {
|
|
50
50
|
super();
|
package/dist/diagnose.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface DiagnosticReport {
|
|
|
31
31
|
skipped: number;
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
declare const SDK_VERSION = "0.2.
|
|
34
|
+
declare const SDK_VERSION = "0.2.81";
|
|
35
35
|
declare function checkNodeVersion(): CheckResult;
|
|
36
36
|
declare function checkPlatform(): CheckResult;
|
|
37
37
|
declare function checkMemory(): CheckResult;
|
package/dist/diagnose.js
CHANGED
|
@@ -63,7 +63,7 @@ const localNetworkAccess_1 = require("./localNetworkAccess");
|
|
|
63
63
|
// ---------------------------------------------------------------------------
|
|
64
64
|
// SDK version — mirrors package.json; kept in sync by CI
|
|
65
65
|
// ---------------------------------------------------------------------------
|
|
66
|
-
const SDK_VERSION = '0.2.
|
|
66
|
+
const SDK_VERSION = '0.2.81';
|
|
67
67
|
exports._SDK_VERSION = SDK_VERSION;
|
|
68
68
|
// ---------------------------------------------------------------------------
|
|
69
69
|
// ANSI helpers (no external deps)
|