repzo 1.0.292 → 1.0.293

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.
Files changed (34) hide show
  1. package/changelog.md +28 -0
  2. package/lib/index.d.ts +244 -2
  3. package/lib/index.js +541 -0
  4. package/lib/types/index.d.ts +4530 -1
  5. package/package.json +1 -1
  6. package/src/index.ts +1270 -0
  7. package/src/oas/activity-ai-object-detection-session-frame.yaml +600 -0
  8. package/src/oas/ai-object-detection-assigned-missions.yaml +283 -0
  9. package/src/oas/ai-object-detection-assignment-rule.yaml +357 -0
  10. package/src/oas/ai-object-detection-category.yaml +338 -0
  11. package/src/oas/ai-object-detection-dataset.yaml +342 -0
  12. package/src/oas/ai-object-detection-detection-settings.yaml +410 -0
  13. package/src/oas/ai-object-detection-inference.yaml +818 -0
  14. package/src/oas/ai-object-detection-label-group.yaml +265 -0
  15. package/src/oas/ai-object-detection-label-report.yaml +366 -0
  16. package/src/oas/ai-object-detection-label.yaml +395 -0
  17. package/src/oas/ai-object-detection-metric-result.yaml +734 -0
  18. package/src/oas/ai-object-detection-metric.yaml +559 -0
  19. package/src/oas/ai-object-detection-mission-results.yaml +370 -0
  20. package/src/oas/ai-object-detection-mission-set.yaml +250 -0
  21. package/src/oas/ai-object-detection-mission.yaml +349 -0
  22. package/src/oas/ai-object-detection-model-version-epoch.yaml +214 -0
  23. package/src/oas/ai-object-detection-model-version-train-agent.yaml +88 -0
  24. package/src/oas/ai-object-detection-model-version.yaml +567 -0
  25. package/src/oas/ai-object-detection-model.yaml +369 -0
  26. package/src/oas/ai-object-detection-segment.yaml +301 -0
  27. package/src/oas/ai-object-detection-session-analysis.yaml +1306 -0
  28. package/src/oas/ai-object-detection-session-election.yaml +193 -0
  29. package/src/oas/ai-object-detection-session-insight.yaml +418 -0
  30. package/src/oas/ai-object-detection-session.yaml +840 -0
  31. package/src/oas/ai-object-detection-settings.yaml +249 -0
  32. package/src/oas/ai-object-detection-task.yaml +1007 -0
  33. package/src/oas/object-detection-analytics-report.yaml +674 -0
  34. package/src/types/index.ts +5156 -1
@@ -0,0 +1,1007 @@
1
+ openapi: 3.0.3
2
+ info:
3
+ title: Repzo API - AI Object Detection Task
4
+ version: 1.0.0
5
+ description: |
6
+ **Object Detection Tasks** are the atomic annotatable unit of the
7
+ AI object-detection pipeline. Each task is one image plus its
8
+ `annotation_groups[]` — versioned sets of 2D bounding-box
9
+ `annotations[]` (each with a `box`, `confidence`, `label_id`, and
10
+ `label_state`). Tasks feed datasets, model-version training, and
11
+ the dashboard review canvas.
12
+
13
+ **Sessions (AR shelf scans).** A task may optionally belong to an
14
+ `ai-object-detection-session` (set via the `session` field). When it
15
+ does, it also carries per-frame AR context in `frame_meta` (pose,
16
+ intrinsics, tracking, depth source), raw `depth_media` /
17
+ `confidence_media` blobs, and `depth_shape`. When such a frame is
18
+ inferred (via `ai-object-detection-inference` with a `model_version`,
19
+ directly or driven by `ai-object-detection-session-analysis`), each
20
+ placeable detection is back-projected into world coordinates: individual
21
+ `annotations[]` gain `world_position`, `world_size`, `depth_at_center`,
22
+ `depth_confidence`, and `placement_confidence` (geometry only — the
23
+ kept/ignored/merged conclusion is made by the analysis stage, not stored
24
+ here). Human-made / edited boxes (`manual` / `auto_edited` groups) get the
25
+ same placement at SAVE time: a box whose geometry no longer matches its
26
+ `placed_box` snapshot is re-placed on the next `PUT`. After a successful
27
+ analysis run, each contributing annotation's `cluster_id` points at the
28
+ concluded object (`objects[]._id`) on the
29
+ `ai-object-detection-session-analysis` document. All of these fields
30
+ are additive and absent on tasks created from the dashboard upload flow.
31
+
32
+ **Who calls it.** Admins and reps from the dashboard (upload, review,
33
+ bulk-approve). Session frames are materialized as tasks by the
34
+ `activity-ai-object-detection-session-frame` intake service.
35
+
36
+ **Multi-tenancy & lifecycle.** Records are scoped by
37
+ `company_namespace[]` (server-injected on create / update). Soft-delete via
38
+ `disabled: true` (DELETE flips the flag). `annotated` is derived on write:
39
+ `true` when any confirmed group has annotations. `creator` is stamped on
40
+ create, `editor` on update. A `PUT` that really changes a session frame's
41
+ human annotations (moved / new boxes, adds / deletes, re-labels, a fresh
42
+ group) schedules a re-analysis of the parent session; flag flips do not.
43
+
44
+ **Key relationships.** References `media-storage`
45
+ (`file_media`, `depth_media`, `confidence_media`, XAI images),
46
+ `ai-object-detection-label` (`annotations.label_id`, `original_label`),
47
+ `ai-object-detection-model-version` (`annotation_groups.model_version`,
48
+ `annotated_by_model_version_code`), `ai-object-detection-dataset`
49
+ (`task_dataset.dataset`), and `ai-object-detection-session` (`session`).
50
+
51
+ **Bulk patch.** `PATCH /ai-object-detection-task` (no id) accepts a
52
+ `patch-action`-style `writeQuery` and applies it to all tasks matching
53
+ the query filters (the same keys as find, e.g. `?session=:id` to approve
54
+ all auto-annotations for a session). Returns `{ nFound, nModified }`.
55
+
56
+ **Population.** `?populatedKeys[]=` supports `file_media`,
57
+ `task_dataset.dataset`, `annotation_groups.model_version`,
58
+ `annotation_groups.annotations.label_id`,
59
+ `annotation_groups.annotations.original_label`,
60
+ `annotation_groups.annotated_by_model_version_code`, `session`,
61
+ `depth_media`, `confidence_media`,
62
+ `annotation_groups.xai.heatmap_media`,
63
+ `annotation_groups.xai.gradcam_media`,
64
+ `annotation_groups.xai.gradcam_per_detection.media`, and
65
+ `annotation_groups.xai.feature_maps.media`. TOP-LEVEL refs
66
+ (`file_media`, `session`, `depth_media`, `confidence_media`) are emitted
67
+ under `<field>_populated` while the ref itself stays an id; every NESTED
68
+ path (under `task_dataset.` / `annotation_groups.`) populates INLINE,
69
+ replacing the id with the document.
70
+ servers:
71
+ - url: https://sv.api.repzo.me
72
+ security:
73
+ - ApiKeyAuth: []
74
+ - JwtAuth: []
75
+ paths:
76
+ /ai-object-detection-task:
77
+ get:
78
+ summary: Find object detection tasks
79
+ operationId: findAiObjectDetectionTasks
80
+ parameters:
81
+ - in: query
82
+ name: _id
83
+ description: |
84
+ Filter by task `_id`. Pass once for a single match, or as
85
+ `?_id[]=...&_id[]=...` for multiple.
86
+ schema:
87
+ oneOf:
88
+ - type: string
89
+ - type: array
90
+ items: { type: string }
91
+ - in: query
92
+ name: session
93
+ description: Filter tasks (frames) belonging to a session `_id`.
94
+ schema:
95
+ oneOf:
96
+ - type: string
97
+ - type: array
98
+ items: { type: string }
99
+ - in: query
100
+ name: frame_meta.frame_id
101
+ description: Filter by the session-scoped frame counter.
102
+ schema:
103
+ oneOf:
104
+ - type: number
105
+ - type: array
106
+ items: { type: number }
107
+ - in: query
108
+ name: task_dataset.dataset
109
+ description: Filter tasks belonging to a dataset `_id`.
110
+ schema:
111
+ oneOf:
112
+ - type: string
113
+ - type: array
114
+ items: { type: string }
115
+ - in: query
116
+ name: annotated
117
+ description: Filter by whether the task has any confirmed annotations.
118
+ schema: { type: boolean }
119
+ - in: query
120
+ name: annotated_label
121
+ description: |
122
+ Only tasks containing at least one annotation (in any group) whose
123
+ `label_id` is one of the given label `_id`s. Used to surface
124
+ candidate crops for a label's reference photo. Non-ObjectId values
125
+ are ignored.
126
+ schema:
127
+ oneOf:
128
+ - type: string
129
+ - type: array
130
+ items: { type: string }
131
+ - in: query
132
+ name: annotation_status
133
+ description: |
134
+ Filter by the task's mutually-exclusive annotation workflow status.
135
+ The filter is applied before counting and pagination. `pending`
136
+ means the task is not annotated (or has no annotation groups);
137
+ `confirmed` means its leading annotation group is confirmed;
138
+ `manual` and `auto_edited` select annotated tasks whose leading
139
+ group is unconfirmed with that state; `auto` selects the remaining
140
+ annotated tasks with an unconfirmed leading group. Any other value
141
+ is rejected with `400`.
142
+ schema:
143
+ type: string
144
+ enum: [pending, confirmed, manual, auto_edited, auto]
145
+ - in: query
146
+ name: disabled
147
+ description: "Include soft-deleted tasks. Omitted / `false` returns active tasks only; `true` returns disabled tasks only."
148
+ schema: { type: boolean, default: false }
149
+ - in: query
150
+ name: from_createdAt
151
+ description: Return tasks created at/after this Unix timestamp (ms) or date string.
152
+ schema: { type: number }
153
+ - in: query
154
+ name: to_createdAt
155
+ description: Return tasks created at/before this Unix timestamp (ms) or date string.
156
+ schema: { type: number }
157
+ - in: query
158
+ name: from_updatedAt
159
+ description: Cursor — tasks with `updatedAt` at/after this Unix timestamp (ms).
160
+ schema: { type: number }
161
+ - in: query
162
+ name: to_updatedAt
163
+ description: Cursor — tasks with `updatedAt` at/before this Unix timestamp (ms).
164
+ schema: { type: number }
165
+ - in: query
166
+ name: populatedKeys
167
+ description: |
168
+ Embed referenced documents. Top-level refs land under
169
+ `<field>_populated`; nested paths populate inline (see the intro).
170
+ schema:
171
+ type: array
172
+ items:
173
+ type: string
174
+ enum:
175
+ - file_media
176
+ - task_dataset.dataset
177
+ - annotation_groups.model_version
178
+ - annotation_groups.annotations.label_id
179
+ - annotation_groups.annotations.original_label
180
+ - annotation_groups.annotated_by_model_version_code
181
+ - session
182
+ - depth_media
183
+ - confidence_media
184
+ - annotation_groups.xai.heatmap_media
185
+ - annotation_groups.xai.gradcam_media
186
+ - annotation_groups.xai.gradcam_per_detection.media
187
+ - annotation_groups.xai.feature_maps.media
188
+ - in: query
189
+ name: per_page
190
+ description: Page size. Defaults to the server's configured pagination limit.
191
+ schema: { type: integer, minimum: 1, maximum: 50000 }
192
+ example: 50
193
+ - in: query
194
+ name: page
195
+ description: 1-indexed page number.
196
+ schema: { type: integer, minimum: 1 }
197
+ example: 1
198
+ - in: query
199
+ name: sort
200
+ description: "Field to sort by. Defaults to `_id`."
201
+ schema: { type: string, default: _id }
202
+ - in: query
203
+ name: sortPageOrder
204
+ description: "Sort direction. Defaults to descending (`dsc`)."
205
+ schema: { type: string, enum: [asc, dsc], default: dsc }
206
+ responses:
207
+ "200":
208
+ description: Paginated list of object detection tasks.
209
+ content:
210
+ application/json:
211
+ schema:
212
+ $ref: "#/components/schemas/TaskFindResult"
213
+ "400":
214
+ description: "Invalid `annotation_status` value."
215
+ post:
216
+ summary: Create an object detection task
217
+ description: |
218
+ Creates a task. `company_namespace` and `creator` are
219
+ server-injected. Each `annotation_group` MUST carry a valid
220
+ `annotation_state` (else `400`): `manual` / `auto_edited` mark the
221
+ group `confirmed` (+ `confirmed_by` from the JWT), `auto` records
222
+ `annotated_by_model_version_code` = `model_version`. A missing
223
+ `time` defaults to now. `annotated` is derived.
224
+ operationId: createAiObjectDetectionTask
225
+ requestBody:
226
+ required: true
227
+ content:
228
+ application/json:
229
+ schema:
230
+ $ref: "#/components/schemas/TaskCreateBody"
231
+ responses:
232
+ "201":
233
+ description: The newly-created task document.
234
+ content:
235
+ application/json:
236
+ schema:
237
+ $ref: "#/components/schemas/TaskSchema"
238
+ "400":
239
+ description: "An annotation group has a missing / invalid `annotation_state`, or model validation failed (e.g. missing `file_media`)."
240
+ patch:
241
+ summary: Bulk-update tasks
242
+ description: |
243
+ Bulk update via the `patch-action` `writeQuery` shape, applied to
244
+ all tasks matching the query filters (the same accepted keys as
245
+ find — no pagination / population / `annotation_status`). Used for
246
+ session-scoped bulk actions (e.g. `?session=:id` with a writeQuery
247
+ that sets annotation groups confirmed). Returns `{ nFound, nModified }`.
248
+ operationId: patchAiObjectDetectionTask
249
+ parameters:
250
+ - in: query
251
+ name: _id
252
+ description: Restrict the bulk update to these task `_id`s.
253
+ schema:
254
+ oneOf:
255
+ - type: string
256
+ - type: array
257
+ items: { type: string }
258
+ - in: query
259
+ name: session
260
+ description: Restrict the bulk update to tasks of this session `_id`.
261
+ schema:
262
+ oneOf:
263
+ - type: string
264
+ - type: array
265
+ items: { type: string }
266
+ - in: query
267
+ name: frame_meta.frame_id
268
+ description: Restrict to the given session-scoped frame counter(s).
269
+ schema:
270
+ oneOf:
271
+ - type: number
272
+ - type: array
273
+ items: { type: number }
274
+ - in: query
275
+ name: task_dataset.dataset
276
+ description: Restrict to tasks belonging to a dataset `_id`.
277
+ schema:
278
+ oneOf:
279
+ - type: string
280
+ - type: array
281
+ items: { type: string }
282
+ - in: query
283
+ name: annotated
284
+ schema: { type: boolean }
285
+ - in: query
286
+ name: disabled
287
+ schema: { type: boolean, default: false }
288
+ - in: query
289
+ name: from_createdAt
290
+ schema: { type: number }
291
+ - in: query
292
+ name: to_createdAt
293
+ schema: { type: number }
294
+ - in: query
295
+ name: from_updatedAt
296
+ schema: { type: number }
297
+ - in: query
298
+ name: to_updatedAt
299
+ schema: { type: number }
300
+ requestBody:
301
+ required: true
302
+ content:
303
+ application/json:
304
+ schema:
305
+ $ref: "#/components/schemas/PatchActionBody"
306
+ responses:
307
+ "200":
308
+ description: Bulk-update result.
309
+ content:
310
+ application/json:
311
+ schema:
312
+ $ref: "#/components/schemas/PatchActionResult"
313
+ "400":
314
+ description: "Unknown `writeQuery[].command`."
315
+ /ai-object-detection-task/{id}:
316
+ get:
317
+ summary: Get a task by id
318
+ operationId: getAiObjectDetectionTask
319
+ parameters:
320
+ - in: path
321
+ name: id
322
+ required: true
323
+ schema: { type: string }
324
+ - in: query
325
+ name: populatedKeys
326
+ description: Same keys as on find.
327
+ schema:
328
+ type: array
329
+ items:
330
+ type: string
331
+ enum:
332
+ - file_media
333
+ - task_dataset.dataset
334
+ - annotation_groups.model_version
335
+ - annotation_groups.annotations.label_id
336
+ - annotation_groups.annotations.original_label
337
+ - annotation_groups.annotated_by_model_version_code
338
+ - session
339
+ - depth_media
340
+ - confidence_media
341
+ - annotation_groups.xai.heatmap_media
342
+ - annotation_groups.xai.gradcam_media
343
+ - annotation_groups.xai.gradcam_per_detection.media
344
+ - annotation_groups.xai.feature_maps.media
345
+ responses:
346
+ "200":
347
+ description: The task document for the given `_id`.
348
+ content:
349
+ application/json:
350
+ schema:
351
+ $ref: "#/components/schemas/TaskSchema"
352
+ "400":
353
+ description: No task with that `_id` in the caller's namespace (note — 400, not 404).
354
+ put:
355
+ summary: Update a task
356
+ description: |
357
+ Standard put — typically to edit annotations during review. The body's
358
+ `annotation_groups` REPLACES the stored array wholesale, so clients
359
+ must resend every group they want to keep; revising an existing group
360
+ means resending it edited in place (not appending a copy).
361
+ `editor` is server-injected; `annotated` is re-derived; `edit_time`
362
+ is restamped on every group; an `auto_edited` group is marked
363
+ `confirmed`; each confirmed group's `confirmed_by` is stamped from
364
+ the JWT only when it doesn't already carry a valid user — the
365
+ original confirmer is preserved across re-saves. On session frames,
366
+ human boxes (`manual` / `auto_edited` groups) whose geometry changed
367
+ since their `placed_box` are re-placed in world coordinates
368
+ (best-effort — a failure records `ignore_reason` and never blocks
369
+ the save), and a real content change schedules a session
370
+ re-analysis.
371
+ operationId: updateAiObjectDetectionTask
372
+ parameters:
373
+ - in: path
374
+ name: id
375
+ required: true
376
+ schema: { type: string }
377
+ requestBody:
378
+ required: true
379
+ content:
380
+ application/json:
381
+ schema:
382
+ $ref: "#/components/schemas/TaskUpdateBody"
383
+ responses:
384
+ "200":
385
+ description: The task document after the update is applied.
386
+ content:
387
+ application/json:
388
+ schema:
389
+ $ref: "#/components/schemas/TaskSchema"
390
+ "404":
391
+ description: No task with that `_id` in the caller's namespace.
392
+ delete:
393
+ summary: Soft-delete a task
394
+ description: "Sets `disabled: true` and returns the task."
395
+ operationId: removeAiObjectDetectionTask
396
+ parameters:
397
+ - in: path
398
+ name: id
399
+ required: true
400
+ schema: { type: string }
401
+ responses:
402
+ "200":
403
+ description: "The task document after soft-deletion (`disabled: true`)."
404
+ content:
405
+ application/json:
406
+ schema:
407
+ $ref: "#/components/schemas/TaskSchema"
408
+ "400":
409
+ description: No task with that `_id` in the caller's namespace.
410
+ components:
411
+ securitySchemes:
412
+ ApiKeyAuth:
413
+ type: apiKey
414
+ in: header
415
+ name: api-key
416
+ description: |
417
+ Server-issued API key. Also accepted via the `x-api-key` header or the
418
+ `?apiKey=` query parameter as fallbacks.
419
+ JwtAuth:
420
+ type: apiKey
421
+ in: header
422
+ name: Authorization
423
+ description: |
424
+ Raw JWT in the `Authorization` header — **no `Bearer ` prefix**.
425
+ Obtained from `POST /authenticate` (admin / rep / client login).
426
+ schemas:
427
+ UserRef:
428
+ type: object
429
+ properties:
430
+ _id: { type: string }
431
+ type: { type: string, enum: [admin, rep] }
432
+ name: { type: string }
433
+ rep: { type: string }
434
+ admin: { type: string }
435
+ Box:
436
+ type: object
437
+ description: "Normalized box. Inference and the dashboard store YOLO center-size here: `x1` = cx, `y1` = cy, `x2` = w, `y2` = h."
438
+ required: [x1, x2, y1, y2]
439
+ properties:
440
+ x1: { type: number }
441
+ x2: { type: number }
442
+ y1: { type: number }
443
+ y2: { type: number }
444
+ Annotation:
445
+ type: object
446
+ required: [box, label_id, label_state]
447
+ properties:
448
+ _id: { type: string }
449
+ box:
450
+ $ref: "#/components/schemas/Box"
451
+ confidence: { type: number, default: 0.25 }
452
+ label_id:
453
+ type: string
454
+ description: "`ai-object-detection-label` id."
455
+ label_state: { type: string, enum: [auto, manual] }
456
+ placed_box:
457
+ allOf:
458
+ - $ref: "#/components/schemas/Box"
459
+ description: |
460
+ The box geometry world placement was last computed FOR. Set by the
461
+ placement pass (inference for auto groups; the task-save pass for
462
+ manual/auto_edited groups). When `box` differs from `placed_box`
463
+ (a human moved/redrew it), the next save recomputes the world
464
+ fields — so every annotation carries AR data from birth.
465
+ world_position:
466
+ type: object
467
+ description: |
468
+ Back-projected centroid in world coordinates, **metres**. Written
469
+ by the PLACEMENT pass: at inference time for model detections, and
470
+ at task-save time for human-made/edited boxes (manual /
471
+ auto_edited groups on session frames with usable
472
+ pose/intrinsics/depth).
473
+ properties:
474
+ x: { type: number }
475
+ y: { type: number }
476
+ z: { type: number }
477
+ world_size:
478
+ type: object
479
+ description: "Physical front-face size of the detection, **centimetres** (`w` = world-horizontal, `h` = vertical)."
480
+ properties:
481
+ w: { type: number }
482
+ h: { type: number }
483
+ depth_at_center:
484
+ type: number
485
+ description: |
486
+ Distance from the camera, **metres** (session frames only) — the
487
+ front-biased percentile (default p30) of the depth pixels sampled
488
+ over the whole detection bbox.
489
+ depth_confidence:
490
+ type: number
491
+ description: Normalized 0..1 depth confidence (session frames only).
492
+ placement_confidence:
493
+ type: number
494
+ description: "detection_conf x depth_conf x tracking (placed session frames only)."
495
+ cluster_id:
496
+ type: string
497
+ description: |
498
+ Link to the analysis object this detection was clustered into. Note:
499
+ the kept/ignored/merged conclusion lives on the
500
+ `ai-object-detection-session-analysis` doc, **not** on the annotation.
501
+ ignore_reason:
502
+ type: string
503
+ enum:
504
+ - no_pose
505
+ - no_intrinsics
506
+ - no_depth
507
+ - empty_depth_region
508
+ - insufficient_depth_pixels
509
+ - behind_shelf
510
+ description: |
511
+ Why this detection could NOT be placed in world coordinates (set by
512
+ the placement pass; unset when placed). Frame-level causes:
513
+ `no_pose` / `no_intrinsics` / `no_depth`. Per-box causes:
514
+ `empty_depth_region` (box maps to no depth pixels),
515
+ `insufficient_depth_pixels` (fewer than 3 valid pixels),
516
+ `behind_shelf` (only background depth beyond the shelf gate).
517
+ original_label:
518
+ type: string
519
+ description: |
520
+ Dims-reclassifier provenance — when the reclassifier moves this
521
+ annotation to a sibling label, the FIRST original label is kept
522
+ here (written once; cleared if a later run reverts to it).
523
+ reclassification_reason:
524
+ type: string
525
+ enum: [dims_match_sibling, group_consensus, manual]
526
+ size_rejected:
527
+ type: boolean
528
+ description: |
529
+ Size-gate provenance — set when the measured physical size exceeded
530
+ the label's expected dims beyond the allowance and the detection
531
+ was dropped from the analysis walk. The annotation is KEPT (never
532
+ deleted); cleared when a later run passes it.
533
+ size_reject_detail:
534
+ type: object
535
+ properties:
536
+ exceeded: { type: string, enum: [width, height, area] }
537
+ measured_w_cm: { type: number }
538
+ measured_h_cm: { type: number }
539
+ expected_w_cm: { type: number }
540
+ expected_h_cm: { type: number }
541
+ ratio:
542
+ type: number
543
+ description: measured / (expected × (1 + allowance)) for the tripped check.
544
+ AnnotationGroup:
545
+ type: object
546
+ properties:
547
+ _id: { type: string }
548
+ model_version: { type: string }
549
+ engine:
550
+ type: string
551
+ enum: [trained, zero_shot]
552
+ description: |
553
+ Which detector produced this group: a trained model version
554
+ (default) or a zero-shot VLM (no trained model yet).
555
+ zero_shot_model:
556
+ type: string
557
+ description: "The zero-shot VLM used, e.g. `qwen/qwen3-vl-8b-instruct`."
558
+ usable:
559
+ type: boolean
560
+ default: true
561
+ description: Opt-out training flag.
562
+ time:
563
+ type: number
564
+ description: Epoch ms; defaults to now on create.
565
+ edit_time:
566
+ type: number
567
+ description: Epoch ms; server-stamped on every update.
568
+ annotation_state: { type: string, enum: [auto, manual, auto_edited] }
569
+ confirmed: { type: boolean, default: false }
570
+ confirmed_by:
571
+ $ref: "#/components/schemas/UserRef"
572
+ annotated_by_model_version_code: { type: string }
573
+ annotations:
574
+ type: array
575
+ items:
576
+ $ref: "#/components/schemas/Annotation"
577
+ inference_run:
578
+ type: string
579
+ description: Link to the session's `inference_runs[]._id` (session inference only).
580
+ session_inference:
581
+ type: object
582
+ description: Per-group session inference metadata (session inference only).
583
+ properties:
584
+ plane_normal:
585
+ type: array
586
+ items: { type: number }
587
+ is_winner_in_cluster: { type: boolean }
588
+ cross_frame_overlap_pct: { type: number }
589
+ xai:
590
+ $ref: "#/components/schemas/GroupXai"
591
+ AnnotationGroupCreate:
592
+ description: "An annotation group as sent on create — `annotation_state` is REQUIRED (a group without a valid one is rejected with 400). Server-stamped fields (`confirmed_by`, `edit_time`, `annotated_by_model_version_code`) are ignored."
593
+ allOf:
594
+ - $ref: "#/components/schemas/AnnotationGroup"
595
+ - type: object
596
+ required: [annotation_state]
597
+ GroupXai:
598
+ type: object
599
+ description: |
600
+ Explainable-AI results persisted by the inference service when the
601
+ group was produced with `explain: true`. Heatmap / feature-map images
602
+ live in media storage — each ref is paired with a `*url` publicUrl
603
+ snapshot so consumers can render without a populate round-trip
604
+ (`populatedKeys[]=annotation_groups.xai.heatmap_media` and
605
+ `annotation_groups.xai.feature_maps.media` populate the refs inline).
606
+ `class_scores` / `embeddings.points` reference the lambda predictions
607
+ by `index` (an unmapped prediction never becomes an annotation, so
608
+ they cannot key to `annotations[]`).
609
+ properties:
610
+ parts:
611
+ type: array
612
+ items: { type: string }
613
+ description: Which XAI parts were requested when the group was made.
614
+ heatmap_media:
615
+ type: string
616
+ description: Media ref of the EigenCAM RGBA PNG (alpha = activation).
617
+ heatmap_url:
618
+ type: string
619
+ description: publicUrl snapshot of `heatmap_media`.
620
+ heatmap_method: { type: string, enum: [eigencam] }
621
+ heatmap_width: { type: integer }
622
+ heatmap_height: { type: integer }
623
+ gradcam_media:
624
+ type: string
625
+ description: "Media ref of the TRUE gradient Grad-CAM RGBA PNG (alpha = activation)."
626
+ gradcam_url:
627
+ type: string
628
+ description: publicUrl snapshot of `gradcam_media`.
629
+ gradcam_width: { type: integer }
630
+ gradcam_height: { type: integer }
631
+ gradcam_per_detection:
632
+ type: array
633
+ description: |
634
+ Per-detection Grad-CAM maps ("why THIS object") — one image per
635
+ top-K detection, same overlay contract as `gradcam_media`.
636
+ `index` references the lambda's predictions like `class_scores`.
637
+ Populate the refs with
638
+ `annotation_groups.xai.gradcam_per_detection.media`.
639
+ items:
640
+ type: object
641
+ properties:
642
+ index: { type: integer }
643
+ class: { type: integer }
644
+ name: { type: string }
645
+ confidence: { type: number }
646
+ box:
647
+ type: object
648
+ description: Detection box in original-image pixels (xyxy).
649
+ properties:
650
+ x1: { type: number }
651
+ y1: { type: number }
652
+ x2: { type: number }
653
+ y2: { type: number }
654
+ match_iou: { type: number }
655
+ media:
656
+ type: string
657
+ description: Media ref of this detection's RGBA PNG map.
658
+ url:
659
+ type: string
660
+ description: publicUrl snapshot of `media`.
661
+ width: { type: integer }
662
+ height: { type: integer }
663
+ feature_maps:
664
+ type: array
665
+ items:
666
+ type: object
667
+ properties:
668
+ stage:
669
+ type: string
670
+ description: "Network stage, e.g. `stage12_C2f`."
671
+ media:
672
+ type: string
673
+ description: Media ref of the stage grid JPEG.
674
+ url:
675
+ type: string
676
+ description: publicUrl snapshot of `media`.
677
+ class_scores:
678
+ type: array
679
+ description: Per-detection top-k class scores (see the inference spec).
680
+ items:
681
+ type: object
682
+ properties:
683
+ index: { type: integer }
684
+ class: { type: integer }
685
+ name: { type: string }
686
+ confidence: { type: number }
687
+ box:
688
+ type: object
689
+ properties:
690
+ x1: { type: number }
691
+ y1: { type: number }
692
+ x2: { type: number }
693
+ y2: { type: number }
694
+ match_iou: { type: number }
695
+ candidates:
696
+ type: array
697
+ items:
698
+ type: object
699
+ properties:
700
+ class: { type: integer }
701
+ name: { type: string }
702
+ score: { type: number }
703
+ embeddings:
704
+ type: object
705
+ description: 2D projection of per-detection embeddings.
706
+ properties:
707
+ method:
708
+ type: string
709
+ enum: [umap, tsne, pca, none]
710
+ description: |
711
+ The projection that actually ran. On `auto` — UMAP for 10+
712
+ detections, t-SNE for 5-9, PCA for 2-4, `none` for a single
713
+ point; explicit requests degrade down the umap -> tsne -> pca
714
+ chain when infeasible.
715
+ requested_method:
716
+ type: string
717
+ enum: [auto, umap, tsne, pca]
718
+ description: "What the caller asked for (differs from `method` on fallback)."
719
+ layer: { type: string }
720
+ embedding_dim: { type: integer }
721
+ points:
722
+ type: array
723
+ items:
724
+ type: object
725
+ properties:
726
+ index: { type: integer }
727
+ class: { type: integer }
728
+ name: { type: string }
729
+ confidence: { type: number }
730
+ x: { type: number }
731
+ y: { type: number }
732
+ confusion_matrix:
733
+ type: object
734
+ description: Training-time confusion-matrix image URLs of the model version.
735
+ properties:
736
+ url: { type: string }
737
+ normalized_url: { type: string }
738
+ source: { type: string, enum: [training_artifacts] }
739
+ notes:
740
+ type: array
741
+ items: { type: string }
742
+ description: Reasons for any XAI part that could not be produced or persisted.
743
+ computed_at:
744
+ type: number
745
+ description: Epoch ms when the XAI results were persisted (withheld when no image could be stored).
746
+ TaskDataset:
747
+ type: object
748
+ required: [dataset, subset]
749
+ properties:
750
+ _id: { type: string }
751
+ dataset: { type: string }
752
+ subset: { type: string, enum: [train, val, test, auto, ignore] }
753
+ FrameMeta:
754
+ type: object
755
+ description: Per-frame AR context, present only on session frames.
756
+ properties:
757
+ frame_id: { type: number }
758
+ ts: { type: number }
759
+ pose:
760
+ type: array
761
+ items: { type: number }
762
+ description: 16-float 4x4 pose matrix, column-major.
763
+ euler_ypr:
764
+ type: array
765
+ items: { type: number }
766
+ intrinsics:
767
+ type: object
768
+ properties:
769
+ fx: { type: number }
770
+ fy: { type: number }
771
+ cx: { type: number }
772
+ cy: { type: number }
773
+ distortion:
774
+ type: object
775
+ properties:
776
+ k1: { type: number }
777
+ k2: { type: number }
778
+ k3: { type: number }
779
+ p1: { type: number }
780
+ p2: { type: number }
781
+ tracking:
782
+ type: object
783
+ properties:
784
+ state: { type: string, enum: [NORMAL, LIMITED, LOST] }
785
+ score: { type: number }
786
+ drift_m: { type: number }
787
+ velocity_mps: { type: number }
788
+ distance_to_shelf_m:
789
+ type: number
790
+ description: Shelf-distance gate used when back-projecting this frame's detections.
791
+ image_rotation_deg:
792
+ type: number
793
+ description: CW degrees the sensor image was rotated to produce the stored image (0/90/180/270).
794
+ image_stats:
795
+ type: object
796
+ properties:
797
+ iso: { type: number }
798
+ shutter: { type: number }
799
+ lux: { type: number }
800
+ sharpness:
801
+ type: number
802
+ description: Variance-of-Laplacian focus measure (higher = sharper).
803
+ depth_summary:
804
+ type: object
805
+ description: Depth-map presence + confidence fractions (election input).
806
+ properties:
807
+ width: { type: number }
808
+ height: { type: number }
809
+ conf_high: { type: number }
810
+ conf_medium: { type: number }
811
+ conf_low: { type: number }
812
+ depth_source: { type: string, enum: [lidar, estimated, none] }
813
+ min_distance_depth:
814
+ type: number
815
+ description: Nearest depth sample in the frame, meters.
816
+ max_distance_depth:
817
+ type: number
818
+ description: Farthest depth sample in the frame, meters.
819
+ depth_variation:
820
+ type: number
821
+ description: Depth span (max - min), meters.
822
+ yaw_degree:
823
+ type: number
824
+ description: Camera yaw at capture, degrees.
825
+ pitch_degree:
826
+ type: number
827
+ description: Camera pitch at capture, degrees.
828
+ roll_degree:
829
+ type: number
830
+ description: Camera roll at capture, degrees.
831
+ frame_sharpness:
832
+ type: number
833
+ description: Variance-of-Laplacian focus measure (higher = sharper; unmeasured frames omit it).
834
+ capture_tier:
835
+ type: string
836
+ enum: [good, warn, error]
837
+ description: Device validator verdict for the frame at capture time.
838
+ detection_settings_polled_at:
839
+ type: number
840
+ description: Epoch ms of the app's last successful detection-settings poll before this frame was submitted.
841
+ app_version:
842
+ type: string
843
+ description: App build that captured the frame.
844
+ TaskSchema:
845
+ type: object
846
+ description: Object detection task document.
847
+ properties:
848
+ _id: { type: string }
849
+ file_media: { type: string }
850
+ shape:
851
+ type: array
852
+ items: { type: number }
853
+ annotation_groups:
854
+ type: array
855
+ items:
856
+ $ref: "#/components/schemas/AnnotationGroup"
857
+ task_dataset:
858
+ type: array
859
+ items:
860
+ $ref: "#/components/schemas/TaskDataset"
861
+ creator:
862
+ $ref: "#/components/schemas/UserRef"
863
+ editor:
864
+ $ref: "#/components/schemas/UserRef"
865
+ annotated: { type: boolean }
866
+ session:
867
+ type: string
868
+ description: Parent session `_id` (session frames only).
869
+ frame_meta:
870
+ $ref: "#/components/schemas/FrameMeta"
871
+ depth_media:
872
+ type: string
873
+ description: Media id of the raw float32 depth blob (session frames only).
874
+ confidence_media:
875
+ type: string
876
+ description: Media id of the raw uint8 confidence blob (session frames only).
877
+ depth_shape:
878
+ type: array
879
+ items: { type: number }
880
+ description: "[width, height] of the depth blob."
881
+ disabled: { type: boolean }
882
+ company_namespace:
883
+ type: array
884
+ items: { type: string }
885
+ description: Tenant key. Server-injected — never accept from clients.
886
+ createdAt: { type: string, format: date-time }
887
+ updatedAt: { type: string, format: date-time }
888
+ file_media_populated:
889
+ type: object
890
+ description: "The `media-storage` document, when `populatedKeys[]=file_media` was requested."
891
+ session_populated:
892
+ type: object
893
+ description: "The `ai-object-detection-session` document, when `populatedKeys[]=session` was requested."
894
+ depth_media_populated:
895
+ type: object
896
+ description: "The `media-storage` document, when `populatedKeys[]=depth_media` was requested."
897
+ confidence_media_populated:
898
+ type: object
899
+ description: "The `media-storage` document, when `populatedKeys[]=confidence_media` was requested."
900
+ TaskCreateBody:
901
+ type: object
902
+ description: |
903
+ Body for creating a task. `creator` is server-injected — do not send
904
+ it. The tenant key (`company_namespace`) is optional for SDK callers
905
+ and is otherwise injected from the caller's session.
906
+ required: [file_media]
907
+ properties:
908
+ file_media: { type: string }
909
+ shape:
910
+ type: array
911
+ items: { type: number }
912
+ annotation_groups:
913
+ type: array
914
+ items:
915
+ $ref: "#/components/schemas/AnnotationGroupCreate"
916
+ task_dataset:
917
+ type: array
918
+ items:
919
+ $ref: "#/components/schemas/TaskDataset"
920
+ session: { type: string }
921
+ frame_meta:
922
+ $ref: "#/components/schemas/FrameMeta"
923
+ depth_media: { type: string }
924
+ confidence_media: { type: string }
925
+ depth_shape:
926
+ type: array
927
+ items: { type: number }
928
+ company_namespace:
929
+ type: array
930
+ items: { type: string }
931
+ description: Optional tenant namespace override for SDK callers.
932
+ TaskUpdateBody:
933
+ type: object
934
+ description: |
935
+ Body for updating a task. `editor` is server-injected and `annotated`
936
+ re-derived. `annotation_groups` replaces the stored array wholesale.
937
+ Set `disabled: true` to soft-delete.
938
+ properties:
939
+ file_media: { type: string }
940
+ shape:
941
+ type: array
942
+ items: { type: number }
943
+ annotation_groups:
944
+ type: array
945
+ items:
946
+ $ref: "#/components/schemas/AnnotationGroup"
947
+ task_dataset:
948
+ type: array
949
+ items:
950
+ $ref: "#/components/schemas/TaskDataset"
951
+ session: { type: string }
952
+ frame_meta:
953
+ $ref: "#/components/schemas/FrameMeta"
954
+ depth_media: { type: string }
955
+ confidence_media: { type: string }
956
+ depth_shape:
957
+ type: array
958
+ items: { type: number }
959
+ disabled: { type: boolean }
960
+ PatchActionWrite:
961
+ type: object
962
+ required: [key, command, value]
963
+ properties:
964
+ key:
965
+ type: string
966
+ description: "Dotted document path, e.g. `annotation_groups.0.confirmed`."
967
+ command:
968
+ type: string
969
+ enum: [set, addToSet, pull]
970
+ description: "`set` → `$set`; `addToSet` → `$addToSet: { $each: value }`; `pull` → `$pull: { $in: value }`."
971
+ value: {}
972
+ PatchActionBody:
973
+ type: object
974
+ description: Bulk-update body. `writeQuery[]` describes the writes applied to filtered rows.
975
+ required: [writeQuery]
976
+ properties:
977
+ writeQuery:
978
+ type: array
979
+ items:
980
+ $ref: "#/components/schemas/PatchActionWrite"
981
+ PatchActionResult:
982
+ type: object
983
+ properties:
984
+ nFound:
985
+ type: number
986
+ description: Matched documents.
987
+ nModified:
988
+ type: number
989
+ description: Modified documents.
990
+ TaskFindResult:
991
+ type: object
992
+ description: Standard paginated result envelope.
993
+ properties:
994
+ data:
995
+ type: array
996
+ items:
997
+ $ref: "#/components/schemas/TaskSchema"
998
+ total_result: { type: number }
999
+ current_count: { type: number }
1000
+ total_pages: { type: number }
1001
+ current_page: { type: number }
1002
+ per_page: { type: number }
1003
+ first_page_url: { type: string }
1004
+ last_page_url: { type: string }
1005
+ next_page_url: { type: string, nullable: true }
1006
+ prev_page_url: { type: string, nullable: true }
1007
+ path: { type: string }