@trillboards/edge-sdk 0.2.95 → 0.2.98

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.
@@ -154,6 +154,13 @@ try {
154
154
  exit 5
155
155
  }
156
156
  1 { Remove-EnrollmentFile; Write-Log "enrollment credential REJECTED - a new one must be issued"; exit 1 }
157
+ 2 {
158
+ # Bad ARGUMENTS (e.g. a malformed --rtsp). Nothing was attempted, so the
159
+ # credential is unspent - and unlike 1 and 4 the file is KEPT, because an
160
+ # operator typo must not cost the store its fleet credential.
161
+ Write-Log "enrollment arguments are invalid - credential PRESERVED, fix the command and re-run"
162
+ exit 2
163
+ }
157
164
  4 { Remove-EnrollmentFile; Write-Log "no enrollment token was readable"; exit 1 }
158
165
  default {
159
166
  Write-Log "enrollment deferred (retryable) - credential PRESERVED for the next-logon retry"
@@ -0,0 +1,482 @@
1
+ /**
2
+ * DetectorFieldProducer — the connector's detector→declared-field producer.
3
+ *
4
+ * ── THE GAP THIS CLOSES ────────────────────────────────────────────────────
5
+ *
6
+ * The Android agent has had one of these for a year: `QueueEstimationProcessor`
7
+ * turns EfficientDet detections into `pedestrian_count` / `queue_length` /
8
+ * `queue_detected`, keyed under the model that produced them
9
+ * (`AudienceSensingService.kt:3090` — `model_outputs.efficientdet.{…}`), and
10
+ * those are exactly the fields `efficientdet` declares in its registry
11
+ * `outputs_schema`.
12
+ *
13
+ * The connector has had none. It runs a real detector on 127.0.0.1:9380 at
14
+ * 15–90 ms a frame, and the only thing that consumes the result is
15
+ * `DetectorShadowProbe`, which collapses the whole detection set to a
16
+ * three-valued enum and discards the boxes. So "how many people are there" —
17
+ * a question the detector answers in milliseconds, with boxes — was answered by
18
+ * asking a language model in prose at ~5,645 ms, at ~9% precision on specifics
19
+ * (docs/runbooks/sensing-catch-rate.md §4a).
20
+ *
21
+ * ── ONE FIELD, ONE PRODUCER ────────────────────────────────────────────────
22
+ *
23
+ * The input is the `producer` half of `modelClassLiveness.partitionProfileFields`
24
+ * — `{field, modelId, type}` per entry, where `modelId` is the model whose
25
+ * registry row DECLARES that field as an output. Ownership is data; this file is
26
+ * only the resolution. Nothing here decides which model answers a field, and it
27
+ * must never acquire that opinion: a second place that can decide is how one
28
+ * fact ends up with two producers, which is the defect the partition exists to
29
+ * remove.
30
+ *
31
+ * The output is TOTAL: `produced.length + withheld.length === fields.length`,
32
+ * always, on every path including the ones where the detector never answered.
33
+ * Same contract, and the same reason, as the partition's own
34
+ * `producer ∪ vlm ∪ withheld === fields`: two independent filters that must
35
+ * agree is precisely how a field goes missing with nothing to point at.
36
+ *
37
+ * This is also why the input is the PARTITION and not the profile's raw
38
+ * `fields[]`. `person_count` is already in `localInferenceSchema`'s
39
+ * `PRESENCE_PRIMITIVE_ORDER`, so every profile asks the language model for it
40
+ * today; a producer handed the raw list would answer it as well and the reading
41
+ * an operator saw would depend on which component wrote last. The partition is
42
+ * what takes a produced field OUT of the prompt, and a producer that could not
43
+ * be told which fields those are could not be correct.
44
+ *
45
+ * ── A GRANTED MODEL WHOSE BYTES NEVER LANDED YIELDS A *MISSING* FIELD ──────
46
+ *
47
+ * A model can be granted by the profile and still be absent from the box: the
48
+ * grant comes from `GET /v2/earner/cameras/sensing-profile` and the artifact
49
+ * arrives separately over `/v1/federated/models`, which was 404 for sixteen days
50
+ * in August.
51
+ *
52
+ * THAT HAS TWO DOORS, and only the first one looks like a failure:
53
+ *
54
+ * the class is unknown to the box — no resident model can emit it, so the
55
+ * service answers **400 before inference** (`server.py::_detect`,
56
+ * deliberately, on ANY unknown class) and `LocalDetectorClient` raises
57
+ * `DetectorUnavailableError`.
58
+ *
59
+ * the class is owned by SOMEBODY ELSE — a different resident model declares
60
+ * it, so the call **succeeds with a 200** and a perfectly good answer from
61
+ * the wrong instrument. Overlap is the norm: `hardhat` is declared by both
62
+ * `ppe_vyra_14cls_v8m` and `ppe_construction_17cls_v8n`, `person` by those
63
+ * two plus `rfdetr_medium`. The attribution check in `produce()` is the only
64
+ * thing that closes this one — see the long note at its call site before
65
+ * deciding it is redundant with the 400.
66
+ *
67
+ * In BOTH the field is then ABSENT. Not `false`, not `0`, and — the important
68
+ * one — NOT handed to the language model instead.
69
+ * `routeEvaluatorService.js:1272` already gets this right one layer up: a
70
+ * focused route reads only its mapped field and yields `no_field` rather than
71
+ * falling back. Substituting a different producer
72
+ * mid-flight is the split-brain again, in TIME instead of space: the same
73
+ * question, answered by two different instruments on two different frames, with
74
+ * nothing on the reading saying which.
75
+ *
76
+ * ── `unavailable` NEVER COLLAPSES INTO `empty` ─────────────────────────────
77
+ *
78
+ * Structural, not a convention: a value is written in exactly one place in this
79
+ * file, from a count derived from a parsed 2xx. There is no path from a caught
80
+ * error to a `false` or a `0`. Every failure — transport, 400, 503, a 2xx whose
81
+ * envelope is not the contract, the budget running out — lands in `withheld`.
82
+ *
83
+ * `LocalDetectorClient` and `DetectorShadowProbe` both carry this distinction in
84
+ * their own headers, and it is the same one: a cascade that reads "detector
85
+ * down" as "nobody there" suppresses every alert while `postCount`,
86
+ * `sensingState:'active'` and the portal's "AI active" tile all stay green
87
+ * (docs/runbooks/cctv-connector-runtime-edge-cases.md).
88
+ *
89
+ * ── ONE CALL PER MODEL, NOT ONE CALL PER FRAME ─────────────────────────────
90
+ *
91
+ * The service refuses a request naming ANY class it cannot resolve — it does not
92
+ * answer the part it recognises — and its own comment says why: "Refusing costs
93
+ * the `person` detections too; that is the right trade." That is correct for ONE
94
+ * question. A frame's declared fields are N INDEPENDENT questions, and unioning
95
+ * them into a single call would make them share a fate they do not share: one
96
+ * specialist whose bytes never landed would take `person_count` down with it.
97
+ *
98
+ * So the classes are grouped by the `modelId` the partition already supplies,
99
+ * one call per model, each carrying the union of the classes THAT model's fields
100
+ * need. A model that cannot answer loses its own fields and no others.
101
+ *
102
+ * This is not the connector holding a class→model catalogue. It cannot and must
103
+ * not: `docs/runbooks/sensing-model-library.md` is explicit that the service
104
+ * owns class→model routing because it is the only component that knows what is
105
+ * resident, and the wire carries `classes` and no model selector. The grouping
106
+ * here is FAILURE ISOLATION over an ownership fact the server sent us.
107
+ *
108
+ * Calls run SEQUENTIALLY. The box has one inference slot; issuing them in
109
+ * parallel queues them inside the service and spends the budget on scheduling.
110
+ *
111
+ * @reuse-audit
112
+ * considered: services/regression/detectorCorpusEndpoint.js
113
+ * - reused, as the SEMANTICS this file is a second implementation of: the
114
+ * `<concept>_present` / `<concept>_count` key shape, `toDetectorClassName`
115
+ * byte-for-byte, and the "trim each class at its own threshold, throw on a
116
+ * label nobody asked for" rule, comments included. NOT imported: that is
117
+ * CommonJS inside the API image and this is the connector's TypeScript
118
+ * bundle; the API does not ship edge-sdk and edge-sdk does not ship the
119
+ * API. What keeps them from drifting is that they answer the same corpus —
120
+ * the six live `expected_output` keys (`person_count`, `person_present`,
121
+ * `forklift_present`, `hardhat_present`, `excavators_present`,
122
+ * `fire_extinguisher_present`) ARE the field names, and
123
+ * `__tests__/DetectorFieldProducer.test.ts` pins this side onto the classes
124
+ * those models declare in `coco_classes`.
125
+ * - ONE DELIBERATE DIVERGENCE, marked at `countsForClasses`: its single-class
126
+ * branch returns `detections.length` unread, and this one reads the label
127
+ * on every branch. That is not a port that drifted — it is the same
128
+ * principle under a different consumer. See the note there before
129
+ * "restoring" it.
130
+ * considered: trillboard-ctv/.../audience/QueueEstimationProcessor.kt
131
+ * - reused: its SHAPE. A processor that turns one frame's detection set into
132
+ * a map keyed by registry-canonical snake_case field names, with the
133
+ * producing model's id travelling ON the values (`model_outputs.<modelId>`)
134
+ * so a reading can be attributed. `toJson`'s note — "keys use
135
+ * registry-canonical snake_case so `chipPersistence.sanitizeFieldKey` is
136
+ * idempotent on canonical input" — is why `produced[].field` is the
137
+ * DECLARED name verbatim and is never re-spelled here.
138
+ * - deliberately NOT ported: its temporal half — `serviceRatePerMinute`,
139
+ * `estimatedWaitTimeMinutes`, `trend`, and the 30-snapshot history they
140
+ * are derived from. Nothing in any producer half declares those fields, so
141
+ * emitting them would advertise a capability no registry row owns — the
142
+ * `yolov8_nano` shape, where a model was offered to ~97 of 114 profile
143
+ * authors while every device hardcoded it unloadable. When a model DOES
144
+ * declare `queue_length`, that is a field with an owner and it arrives
145
+ * here as an ordinary producer entry.
146
+ * considered: edge-sensing/src/processors/FaceDetector.ts — `nonMaxSuppression`
147
+ * + `iou` (the kernel `LocalDetectorClient`'s header names as the one to use)
148
+ * - deliberately NOT used, and this is the one worth reading twice. The
149
+ * SERVICE already ran NMS: `deploy/detector/trillboards_detector/decode.py`
150
+ * does class-offset greedy NMS at `NMS_IOU_THRESHOLD = 0.7` on the anchor
151
+ * branch, transcribed from ultralytics, and deliberately runs NONE on the
152
+ * DETR branch because "running NMS over these rows would be a second
153
+ * suppression the model was neither trained nor scored with". A pass here
154
+ * would be exactly that second suppression, on both branches, and it would
155
+ * move the counts away from the ones the committed score cards fitted their
156
+ * thresholds against. The kernel is fine; a fourth suppression is not.
157
+ * considered: edge-core `CentroidTracker` (the shared nearest-centroid tracker)
158
+ * - deliberately NOT used: no field in any producer half needs cross-frame
159
+ * identity. `<concept>_present` and `<concept>_count` are both properties
160
+ * of ONE frame — which is also how the Android processor computes
161
+ * `estimatedQueueLength` (`current.faceCount`, the latest snapshot). Wiring
162
+ * a tracker in for fields that do not ask for one would add per-feed state
163
+ * with no consumer. When dwell or a unique count lands, that tracker is the
164
+ * one to call — it is already the consolidation of three, and a fourth is
165
+ * what its own header exists to prevent.
166
+ * considered: this package's `zoneContainment.countInZone`
167
+ * - not used HERE: it takes a frame size and a normalized rect, and with a
168
+ * null rect it IS `boxes.length`. A field focused to an operator's drawn
169
+ * region is the natural next consumer of both files at once — the count
170
+ * becomes `countInZone(boxes, frame, rect)` and nothing else changes — but
171
+ * a producer entry carries no region today and inventing one would be a
172
+ * second home for a fact `sensing_route_screens` already owns.
173
+ * considered: `DetectorShadowProbe`
174
+ * - not extended, and not called: it is a MEASUREMENT that must change
175
+ * nothing, with a backoff and a 15 s per-feed cadence bound tuned to that
176
+ * job. A producer answering an operator's declared field is a PRODUCT path
177
+ * with the opposite cadence obligation.
178
+ * considered: edge-core `ModelManager.hasModel(modelId)` — the connector's own
179
+ * "is this artifact on disk" check (`fs.existsSync` over the registered path)
180
+ * - deliberately NOT used as the arrival gate. It answers about the
181
+ * CONNECTOR's download directory; what decides whether a class can be
182
+ * answered is the SERVICE's `ClassIndex`, which `labels.py` builds only
183
+ * over models whose artifact is on the box. Those are two opinions about
184
+ * one fact and they can disagree — a file present but the wrong graph
185
+ * (`ModelContractError`), or resident in a service started before the
186
+ * download. Pre-checking here would also mean a `hasModel` that returned
187
+ * true still has to handle the 400, so the check would be advisory at best
188
+ * and a second catalogue at worst. The 400 IS the arrival check, from the
189
+ * only component that knows.
190
+ */
191
+ import { LocalDetectorClient } from './LocalDetectorClient';
192
+ /**
193
+ * One entry of the `producer` half of a field partition.
194
+ *
195
+ * Mirrors what `modelClassLiveness.partitionProfileFields` puts in `producer[]`:
196
+ * the profile's own field entry, plus the `modelId` that owns it and the type
197
+ * the owning registry row declared.
198
+ *
199
+ * `type` is `string | null` rather than `WireSensingProfileFieldType` ON PURPOSE.
200
+ * The profile's `fields[].type` is that closed enum, but a producer entry's type
201
+ * comes from `sensing_model_registry.outputs_schema[].type`, which nothing
202
+ * validates on write — `declaredOutputFields` reads it as `o.type ?? null` and
203
+ * says so. Declaring it narrower here would be this file asserting a contract
204
+ * the data does not have.
205
+ */
206
+ export interface ProducerField {
207
+ /** The DECLARED field name, verbatim. Never re-spelled — see the reuse audit. */
208
+ field: string;
209
+ /** `sensing_model_registry.model_id` of the model that declares this field. */
210
+ modelId: string;
211
+ /** The declared type, or null when the owning row carried none. */
212
+ type?: string | null;
213
+ }
214
+ /** What a detector can produce: a presence boolean or an instance count. */
215
+ export type ProducedFieldValue = boolean | number;
216
+ /**
217
+ * One field this frame answered, with everything needed to attribute it.
218
+ *
219
+ * `minConfidence` travels WITH the value rather than being restated by whoever
220
+ * reads it, for the reason `DetectorShadowProbe.minConfidence` does: a fleet
221
+ * mid-upgrade runs two operating points at once, and pooling readings taken at
222
+ * two thresholds averages two different classifiers with nothing saying so.
223
+ */
224
+ export interface ProducedField {
225
+ field: string;
226
+ value: ProducedFieldValue;
227
+ /** The model the PARTITION said owns this field. */
228
+ modelId: string;
229
+ /** The class asked for, in the service's wire spelling. */
230
+ className: string;
231
+ /** The confidence floor this value was counted at. */
232
+ minConfidence: number;
233
+ /** The artifact that actually answered, as it names itself. Null if it did not say. */
234
+ detectorModel: string | null;
235
+ }
236
+ /** Why a declared field has no reading this frame. */
237
+ export declare const PRODUCER_WITHHOLD_REASONS: {
238
+ /**
239
+ * The field name is not `<concept>_present` / `<concept>_count`, so no
240
+ * detector class can be derived from it.
241
+ *
242
+ * WITHHELD RATHER THAN DROPPED. A registry row may declare `queue_length` or
243
+ * `vehicle_trend` — `efficientdet` declares both — and a detection set answers
244
+ * neither. Guessing a class out of `trend` is how a bus gets counted as a
245
+ * person; silently dropping it is how a field goes missing with nothing to
246
+ * point at.
247
+ */
248
+ readonly UNMAPPED: "unmapped";
249
+ /**
250
+ * The declared type contradicts what the name asks for — a `_present` field
251
+ * declared `counter`, say.
252
+ *
253
+ * The suffix decides the COMPUTATION and the type is the published contract;
254
+ * when they disagree the row is defective and either answer is wrong. Refusing
255
+ * names the defect instead of writing a boolean into a slot rendered as a
256
+ * number.
257
+ */
258
+ readonly TYPE_CONTRADICTS_NAME: "type_contradicts_name";
259
+ /**
260
+ * The detector did not answer for this field's model.
261
+ *
262
+ * Transport, 400 (a class no resident model can emit — i.e. a granted model
263
+ * whose bytes never landed), 503, a 2xx whose envelope is not the contract, a
264
+ * response labelled with a class nobody asked for, or the budget running out.
265
+ * All of them mean the same thing to a consumer: there is NO READING. None of
266
+ * them may become a value.
267
+ */
268
+ readonly DETECTOR_UNAVAILABLE: "detector_unavailable";
269
+ /**
270
+ * The entry names no model.
271
+ *
272
+ * Its own reason rather than `unmapped`, because it is a defect in a different
273
+ * place: the NAME was readable and it is the OWNERSHIP that is missing. The
274
+ * partition cannot emit such an entry — it only puts a field in `producer[]`
275
+ * once it has found an owner — so this is a claim about a caller, and saying
276
+ * "no class follows from that name" about it would send the reader to the
277
+ * registry's `outputs_schema` when the fault is upstream of it.
278
+ *
279
+ * It is refused rather than answered anonymously. The class list would resolve
280
+ * fine and the count would be correct; what would be lost is attribution, and
281
+ * "a detection whose producer cannot be named is a detection that cannot be
282
+ * attributed when a specialist later turns out to be wrong"
283
+ * (`deploy/detector/trillboards_detector/detections.py`).
284
+ */
285
+ readonly UNOWNED: "unowned";
286
+ /**
287
+ * The same field name appeared twice in the producer half.
288
+ *
289
+ * The first occurrence is answered and the rest are withheld, so the total
290
+ * contract holds over the input array exactly. A repeated field is the "one
291
+ * fact, two answers" defect at its smallest scale and it is worth a name.
292
+ */
293
+ readonly DUPLICATE: "duplicate";
294
+ };
295
+ export type ProducerWithholdReason = (typeof PRODUCER_WITHHOLD_REASONS)[keyof typeof PRODUCER_WITHHOLD_REASONS];
296
+ export interface WithheldProducerField {
297
+ field: string;
298
+ modelId: string;
299
+ reason: ProducerWithholdReason;
300
+ detail: string;
301
+ }
302
+ /**
303
+ * One frame's answer.
304
+ *
305
+ * `produced.length + withheld.length === fields.length`. Always.
306
+ */
307
+ export interface ProducedFieldSet {
308
+ produced: ProducedField[];
309
+ withheld: WithheldProducerField[];
310
+ }
311
+ /**
312
+ * The slice of a detector this producer uses.
313
+ *
314
+ * Picked off `LocalDetectorClient` itself rather than re-declared, so a change
315
+ * to `detect`'s signature breaks here too — and narrowed to the one method,
316
+ * because this producer never probes health. `DetectorShadowProbe` owns liveness
317
+ * and backoff; a producer that also had an opinion about when the service is up
318
+ * would be a second such opinion, and requiring an `isHealthy` stub in every
319
+ * test would imply one exists.
320
+ */
321
+ export type DetectorFieldClient = Pick<LocalDetectorClient, 'detect'>;
322
+ /** Builds a client for ONE model group's classes and operating point. */
323
+ export type DetectorClientFactory = (options: {
324
+ classes: string[];
325
+ minConfidence: number;
326
+ timeoutMs: number;
327
+ }) => DetectorFieldClient;
328
+ /**
329
+ * The device's operating point when a model measured none of its own. 0.25.
330
+ *
331
+ * `LocalDetectorClient`'s default, and a FALLBACK rather than an answer — the
332
+ * same constant and the same caveat as
333
+ * `detectorCorpusEndpoint.DEFAULT_MIN_CONFIDENCE`. A model whose score card
334
+ * SELECTED a threshold must be counted at THAT threshold or the field is
335
+ * produced by a classifier no committed measurement describes: `gh_forklift_combine`
336
+ * fits 0.7435 and `rfdetr_medium`'s person gate 0.2939, and at 0.25 the
337
+ * published precision/recall simply do not apply to what shipped.
338
+ */
339
+ export declare const DEFAULT_PRODUCER_MIN_CONFIDENCE = 0.25;
340
+ /**
341
+ * Per-call transport timeout. 1000 ms, matching `DEFAULT_SHADOW_TIMEOUT_MS`.
342
+ *
343
+ * NOT `LocalDetectorClient`'s own 5 s default: this runs on the path to an
344
+ * analysis, and the detector's measured cost is ~63 ms p50 / ~79 ms p95, so
345
+ * 1000 ms is >12x headroom for the slowest plausible answer.
346
+ */
347
+ export declare const DEFAULT_PRODUCER_TIMEOUT_MS = 1000;
348
+ /**
349
+ * TOTAL budget across every model call for one frame. 1500 ms.
350
+ *
351
+ * A total and not a per-call bound, because the question it answers is "how long
352
+ * may producing this frame's fields hold the tick", and that is a property of
353
+ * the frame, not of one model. At the measured p95 a four-model portfolio costs
354
+ * ~316 ms, so this is ~4.7x headroom and cannot bind on a healthy box.
355
+ *
356
+ * When it DOES bind, the models that never got their call are withheld
357
+ * `detector_unavailable` — a missing field, which is the same discipline the
358
+ * 400 case gets and for the same reason. A budget that returned a value it did
359
+ * not measure would be worse than one that stalls.
360
+ *
361
+ * It bounds the number of calls STARTED and, via the per-call timeout below,
362
+ * how long each may sit idle. It does not close the one hole
363
+ * `LocalDetectorClient`'s header names — a server dribbling bytes slowly under a
364
+ * large Content-Length keeps the socket busy so the inactivity timer never fires
365
+ * — because that hole is in the transport and belongs to every client in this
366
+ * package; two call-site races for it already exist and a third is not the fix.
367
+ */
368
+ export declare const DEFAULT_PRODUCE_DEADLINE_MS = 1500;
369
+ /**
370
+ * The detector's canonical wire spelling.
371
+ *
372
+ * `normalize_class_name` in
373
+ * `deploy/detector/trillboards_detector/labels.py` is
374
+ * `name.strip().lower().replace("_", " ").replace("-", " ")`, and `labels.py`
375
+ * REFUSES to import a vocabulary whose names are not already folded — so this is
376
+ * the only spelling `/detect` resolves. A field carries its concept in the
377
+ * spelling a JSON key can hold (`fire_extinguisher`); the service is asked in
378
+ * the spelling it demands (`fire extinguisher`). One fold, one place.
379
+ *
380
+ * Byte-identical to `detectorCorpusEndpoint.toDetectorClassName`, deliberately:
381
+ * the corpus that GATES a model's rollout and the producer that READS it in the
382
+ * field must ask for the same class, or the thing measured is not the thing that
383
+ * ships.
384
+ */
385
+ export declare const toDetectorClassName: (name: string) => string;
386
+ /** The producible key shape: `<concept>_present`, `<concept>_count`. */
387
+ export declare const CONCEPT_KEY_RE: RegExp;
388
+ /**
389
+ * How the service joins the names of the models that answered one frame.
390
+ *
391
+ * `MODEL_JOIN` in `deploy/detector/trillboards_detector/server.py`, where
392
+ * `model` is `MODEL_JOIN.join(batch.models)` — one name on a single-model box, N
393
+ * for a request the registry split across several. Held here because the
394
+ * attribution check below has to take that string apart again, and a wrong
395
+ * separator would silently make every merged answer look unattributed.
396
+ */
397
+ export declare const DETECTOR_MODEL_JOIN = "+";
398
+ /** A producer field this detector can answer, once its name has been read. */
399
+ export interface MappedProducerField extends ProducerField {
400
+ /** The concept, in the field's own identifier spelling. */
401
+ concept: string;
402
+ /** The class to ask for, in the service's wire spelling. */
403
+ className: string;
404
+ kind: 'present' | 'count';
405
+ }
406
+ /**
407
+ * Read a declared field name, or say why it cannot be read.
408
+ *
409
+ * The SUFFIX decides the computation — that is the vocabulary the corpus is
410
+ * written in and the only self-describing signal on the entry. The declared type
411
+ * is then checked AGAINST it rather than consulted instead of it: a null type is
412
+ * fine (plenty of registry rows carry none), and a type that contradicts the
413
+ * name is a defective row rather than an instruction.
414
+ */
415
+ export declare function classifyProducerField(entry: ProducerField): MappedProducerField | {
416
+ reason: ProducerWithholdReason;
417
+ detail: string;
418
+ };
419
+ /**
420
+ * How many instances of each asked class this answer contains.
421
+ *
422
+ * Ported decision-for-decision from `detectorCorpusEndpoint.countsForConcepts`,
423
+ * because the corpus that gates a rollout and the producer that reads the model
424
+ * in the field must count the same way.
425
+ *
426
+ * @throws when the service returned a label nobody asked for — see below.
427
+ */
428
+ export declare function countsForClasses(classes: string[], detections: Array<{
429
+ label: string;
430
+ confidence: number;
431
+ }>, classThresholds?: Map<string, number>): Map<string, number>;
432
+ export interface DetectorFieldProducerOptions {
433
+ /** Detector service base URL. Defaults to the client's loopback default. */
434
+ baseUrl?: string;
435
+ /** Per-call transport timeout. Default DEFAULT_PRODUCER_TIMEOUT_MS. */
436
+ timeoutMs?: number;
437
+ /** Total budget for one frame's calls. Default DEFAULT_PRODUCE_DEADLINE_MS. 0 disables. */
438
+ deadlineMs?: number;
439
+ /**
440
+ * Per-class operating points in WIRE spelling, as the models' own score cards
441
+ * fitted them — `modelClassLiveness.livenessFromScoreCard` writes them and
442
+ * `detectorCorpusEndpoint.thresholdsFromLivenessEvidence` folds them. A class
443
+ * with no entry is counted at `minConfidence`.
444
+ */
445
+ classThresholds?: Map<string, number>;
446
+ /** Floor for a class whose model measured none. Default DEFAULT_PRODUCER_MIN_CONFIDENCE. */
447
+ minConfidence?: number;
448
+ /** Clock injection for tests. Default Date.now. */
449
+ now?: () => number;
450
+ }
451
+ export declare class DetectorFieldProducer {
452
+ private readonly baseUrl;
453
+ private readonly timeoutMs;
454
+ private readonly deadlineMs;
455
+ private readonly classThresholds;
456
+ private readonly minConfidence;
457
+ private readonly now;
458
+ private readonly clientFor;
459
+ constructor(options?: DetectorFieldProducerOptions, clientFactory?: DetectorClientFactory);
460
+ /**
461
+ * Produce this frame's declared fields.
462
+ *
463
+ * NEVER THROWS and never returns a promise that rejects: it would sit on the
464
+ * path to the only thing that produces alerts, and a producer that can fail a
465
+ * capture tick is worse than one that reports it has no reading.
466
+ *
467
+ * @param jpeg the frame's bytes
468
+ * @param fields the `producer` half of this profile's field partition
469
+ */
470
+ produce(jpeg: Buffer, fields: ProducerField[]): Promise<ProducedFieldSet>;
471
+ /**
472
+ * Every field of one model, withheld together.
473
+ *
474
+ * A model that did not answer did not answer for ANY of its fields. Booking
475
+ * them one at a time would invite a partial group, which is the collapse this
476
+ * whole file is written against.
477
+ */
478
+ private withholdGroup;
479
+ }
480
+ /** Flatten an answer to the `{field: value}` map a payload carries. */
481
+ export declare function toFieldValues(produced: ProducedField[]): Record<string, ProducedFieldValue>;
482
+ //# sourceMappingURL=DetectorFieldProducer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DetectorFieldProducer.d.ts","sourceRoot":"","sources":["../src/DetectorFieldProducer.ts"],"names":[],"mappings":"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"}