repzo 1.0.291 → 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 +4533 -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 +5159 -1
@@ -0,0 +1,734 @@
1
+ openapi: 3.0.3
2
+ info:
3
+ title: Repzo API - AI Object Detection Metric Result
4
+ version: 1.0.0
5
+ description: |
6
+ **Metric results** — one document per (analysis, metric), holding TWO
7
+ deliberately separate value layers:
8
+
9
+ - `computed` — what the engine calculated from the AI analysis (always
10
+ includes `answer` + `score` 0..1). Machine-owned: every evaluation
11
+ replaces it and humans can NEVER modify it through the API.
12
+ - `overwrite` + `flag` + `confirmed_edit` — the human layer: sparse VALUE
13
+ overrides of the type's overwritable keys (ratio and score are DERIVED
14
+ from them server-side, never accepted), a boolean attention marker,
15
+ and the admin review gate. Human-owned: evaluations never touch them,
16
+ so an override keeps ruling across recalculations.
17
+
18
+ Both layers are STRICT per-type shapes (Mongoose embedded discriminators
19
+ keyed by the metric type — the promotions model pattern); the stored
20
+ subdocuments carry an internal `type` mirror. No loose payloads.
21
+
22
+ **Output families** fix what `answer` means (`output` on the document):
23
+ - `compatibility` (adjacent_block) — `answer` is a boolean verdict;
24
+ score = answer ? 1 : 0.
25
+ - `numerical` (facings_count, on_shelf_availability) — `answer` is the
26
+ collected value. BOTH types accept an optional `target_answer`: the
27
+ score is then answer ÷ target (clamped to 1). Without one, OSA returns
28
+ its own factor (availability = answer ÷ total, so mission points =
29
+ weight × availability) and facings_count falls back to the family
30
+ default: a NON-ZERO answer earns full score, a missing or zero answer
31
+ scores 0.
32
+ - `share_of_shelf` — `answer` is the MAIN segment's measured quantity
33
+ (cm, cm² or facings), `total` the CONSIDERED CATEGORY (the same
34
+ measure over facings whose label belongs to ANY of the metric's
35
+ segments — the rest of the shelf never dilutes the share), `ratio`
36
+ their quotient, `target_answer` = target_ratio × total, and score =
37
+ the main segment's min(1, ratio / target_ratio). Every segment row
38
+ (main + context) is reported as a SegmentOutput in
39
+ `computed.segments[]`.
40
+
41
+ `score` / `answer` / `ratio` on the document are the DENORMALIZED
42
+ EFFECTIVE values — the CONFIRMED `overwrite.*` when `confirmed_edit` is
43
+ true, else `computed.*` — the numbers mission scoring, widgets and
44
+ analytics read. An unconfirmed override is stored and visible but does
45
+ NOT change them.
46
+
47
+ **How results appear.** Automatically: every successful session analysis
48
+ (and every compose-only shelf recompute) evaluates the MISSION-ASSIGNED
49
+ metrics — the stamped mission's list for "SCAN THIS MISSION" sessions,
50
+ the union over the client's currently-assigned missions for generic
51
+ scans. Sessions no mission covers get NO results. Manually:
52
+ `POST { analysis }` recalculates the same assigned set on demand —
53
+ idempotent and safe (an optional `metrics[]` subset can only NARROW the
54
+ assigned set). `source_fingerprints` snapshots the analysis's stage
55
+ fingerprints at evaluation time, so a mismatch marks the result stale.
56
+ Each result also carries a denormalized SCAN CONTEXT (`user`/`user_name`
57
+ = the scanning rep, `client`/`client_name`, `teams`, `visit_id`,
58
+ `route`, `business_day`) in the activity-model shape so shared
59
+ dashboard filters work unchanged.
60
+
61
+ **Re-analysis & the human layer.** A NEW analysis of the same session
62
+ CARRIES the previous analysis's human layer forward (flag,
63
+ confirmed_edit, and the entered override VALUES — an override asserts
64
+ the SCENE's truth, not one machine attempt's), with the derived
65
+ ratio/score re-computed against the fresh machine values. Every
66
+ evaluation and every update here also re-syncs the STORED mission
67
+ results (`/ai-object-detection-mission-results`) of the analysis.
68
+
69
+ **Multi-tenancy, roles & lifecycle.** Scoped by `company_namespace`
70
+ (inherited from the analysis — there is no namespace-injection hook and
71
+ no create body field for it), soft-deleted via `disabled`. `update`
72
+ accepts ONLY `flag`, `overwrite` and (admin-only) `confirmed_edit`;
73
+ `PATCH` is not supported (400). ADMINS have full access. REPS read
74
+ results of THEIR OWN sessions (a `session` or `analysis` filter is
75
+ required on list reads), may raise `flag` (never clear it) and may
76
+ submit value overrides — a rep override is auto-flagged and stays
77
+ PENDING (`confirmed_edit: false`, not effective) until an admin confirms
78
+ it; recalculation and removal are admin-only. Optional population of
79
+ `metric`, `analysis` and `session` via `populatedKeys[]` (the populated
80
+ document replaces the id in place).
81
+ servers:
82
+ - url: https://sv.api.repzo.me
83
+ security:
84
+ - ApiKeyAuth: []
85
+ - JwtAuth: []
86
+ paths:
87
+ /ai-object-detection-metric-result:
88
+ get:
89
+ summary: List metric results
90
+ operationId: findAiObjectDetectionMetricResult
91
+ parameters:
92
+ - in: query
93
+ name: _id
94
+ schema:
95
+ oneOf:
96
+ - type: string
97
+ - type: array
98
+ items: { type: string }
99
+ - in: query
100
+ name: analysis
101
+ description: Filter by the analysis _id (the common read). Required for rep tokens unless `session` is given.
102
+ schema:
103
+ oneOf:
104
+ - type: string
105
+ - type: array
106
+ items: { type: string }
107
+ - in: query
108
+ name: session
109
+ description: Filter by session _id. Rep tokens must own the session.
110
+ schema:
111
+ oneOf:
112
+ - type: string
113
+ - type: array
114
+ items: { type: string }
115
+ - in: query
116
+ name: metric
117
+ schema:
118
+ oneOf:
119
+ - type: string
120
+ - type: array
121
+ items: { type: string }
122
+ - in: query
123
+ name: user
124
+ description: Filter by the SCANNING rep's id (denormalized scan context).
125
+ schema:
126
+ oneOf:
127
+ - type: string
128
+ - type: array
129
+ items: { type: string }
130
+ - in: query
131
+ name: client
132
+ description: Filter by the scanned client's id (denormalized scan context).
133
+ schema:
134
+ oneOf:
135
+ - type: string
136
+ - type: array
137
+ items: { type: string }
138
+ - in: query
139
+ name: teams
140
+ description: "Filter by the scanning rep's team id(s) — pass once or as `?teams[]=`."
141
+ schema:
142
+ oneOf:
143
+ - type: string
144
+ - type: array
145
+ items: { type: string }
146
+ - in: query
147
+ name: type
148
+ schema:
149
+ type: string
150
+ enum:
151
+ [
152
+ adjacent_block,
153
+ facings_count,
154
+ on_shelf_availability,
155
+ share_of_shelf,
156
+ ]
157
+ - in: query
158
+ name: flag
159
+ description: Only flagged (or unflagged) results.
160
+ schema: { type: boolean }
161
+ - in: query
162
+ name: status
163
+ schema:
164
+ type: string
165
+ enum: [ok, error]
166
+ - in: query
167
+ name: search
168
+ description: Case-insensitive substring search on the metric-name snapshot `name`.
169
+ schema: { type: string }
170
+ - in: query
171
+ name: disabled
172
+ description: "Include soft-deleted results (`true`) or only live ones (`false`). Omitted = no filter."
173
+ schema: { type: boolean }
174
+ - in: query
175
+ name: from_updatedAt
176
+ schema: { type: number }
177
+ - in: query
178
+ name: to_updatedAt
179
+ schema: { type: number }
180
+ - in: query
181
+ name: from_createdAt
182
+ schema: { type: number }
183
+ - in: query
184
+ name: to_createdAt
185
+ schema: { type: number }
186
+ - in: query
187
+ name: populatedKeys
188
+ description: "Embed refs in place of the id. Supported: `metric`, `analysis`, `session`."
189
+ schema:
190
+ type: array
191
+ items:
192
+ type: string
193
+ enum: [metric, analysis, session]
194
+ - in: query
195
+ name: per_page
196
+ schema: { type: integer, minimum: 1 }
197
+ - in: query
198
+ name: page
199
+ schema: { type: integer, minimum: 1 }
200
+ - in: query
201
+ name: sort
202
+ description: Field to sort by (default `_id`).
203
+ schema: { type: string }
204
+ - in: query
205
+ name: sortPageOrder
206
+ schema:
207
+ type: string
208
+ enum: [asc, dsc]
209
+ responses:
210
+ "200":
211
+ description: Paginated results.
212
+ content:
213
+ application/json:
214
+ schema:
215
+ $ref: "#/components/schemas/MetricResultFindResult"
216
+ "403":
217
+ description: Rep token without a `session`/`analysis` filter, or the session is not the rep's own.
218
+ post:
219
+ summary: Calculate / recalculate metrics for an analysis (admin)
220
+ description: |
221
+ Evaluates every ENABLED, MISSION-ASSIGNED metric (or the given subset
222
+ of them) against the analysis and upserts one result per metric.
223
+ Idempotent: `computed` is replaced, standing `flag`/`overwrite`
224
+ values are preserved and keep ruling the effective `score` while
225
+ confirmed. Also upserts the stored mission results of the session.
226
+ The tenant namespace comes from the caller's token — there is no
227
+ `company_namespace` body field.
228
+ operationId: createAiObjectDetectionMetricResult
229
+ requestBody:
230
+ required: true
231
+ content:
232
+ application/json:
233
+ schema:
234
+ type: object
235
+ required: [analysis]
236
+ properties:
237
+ analysis:
238
+ type: string
239
+ description: The SUCCESSFUL analysis to evaluate.
240
+ metrics:
241
+ type: array
242
+ items: { type: string }
243
+ description: Optional metric-id subset (default = every mission-assigned enabled metric; can only narrow that set).
244
+ responses:
245
+ "201":
246
+ description: Evaluation summary + the fresh result documents.
247
+ content:
248
+ application/json:
249
+ schema:
250
+ type: object
251
+ properties:
252
+ analysis: { type: string }
253
+ evaluated:
254
+ type: number
255
+ description: Metrics evaluated.
256
+ errors:
257
+ type: number
258
+ description: Metrics whose evaluation or persistence failed.
259
+ results:
260
+ type: array
261
+ items:
262
+ $ref: "#/components/schemas/MetricResult"
263
+ "400":
264
+ description: "`analysis` missing, not found, or not in `success` status."
265
+ "403":
266
+ description: Rep tokens may not recalculate.
267
+ /ai-object-detection-metric-result/{id}:
268
+ get:
269
+ summary: Get a metric result
270
+ operationId: getAiObjectDetectionMetricResult
271
+ parameters:
272
+ - in: path
273
+ name: id
274
+ required: true
275
+ schema: { type: string }
276
+ responses:
277
+ "200":
278
+ description: The result document (refs are never populated on this read).
279
+ content:
280
+ application/json:
281
+ schema:
282
+ $ref: "#/components/schemas/MetricResult"
283
+ "400":
284
+ description: Not found.
285
+ "403":
286
+ description: Rep token — the result's session is not the rep's own.
287
+ put:
288
+ summary: Human override — flag / overwrite / confirmed_edit
289
+ description: |
290
+ The ONLY writable fields are `flag`, `overwrite` and (admin-only)
291
+ `confirmed_edit`. `overwrite` accepts VALUE keys only — `ratio` and
292
+ `score` are rejected and instead DERIVED server-side from the
293
+ effective values by the type's own rule (SOS: ratio = answer ÷ total,
294
+ score = ratio ÷ target_ratio clamped; OSA: ratio = answer ÷ total,
295
+ score = answer ÷ target or the ratio; facings: answer ÷ target or
296
+ existence; compatibility: answer ? 1 : 0). `computed` is immutable
297
+ from the API — the AI-calculated values always remain visible next to
298
+ the override. Enterable keys per type:
299
+ - `adjacent_block`: `answer` (boolean), `member_count`, `cut_count`
300
+ - `facings_count`: `answer` (number)
301
+ - `on_shelf_availability`: `answer` (number)
302
+ - `share_of_shelf`: `answer`, `total`
303
+
304
+ REVIEW FLOW: any fresh `overwrite` resets `confirmed_edit` to false
305
+ (pending) unless the same ADMIN request sets it true; the override
306
+ only drives the effective `score` while confirmed. REP rules: only
307
+ results of the rep's own sessions; `flag` may be raised but not
308
+ cleared; sending a value override auto-raises `flag`;
309
+ `confirmed_edit` is rejected. The stored mission results of the
310
+ analysis are re-synced afterwards.
311
+ operationId: updateAiObjectDetectionMetricResult
312
+ parameters:
313
+ - in: path
314
+ name: id
315
+ required: true
316
+ schema: { type: string }
317
+ requestBody:
318
+ required: true
319
+ content:
320
+ application/json:
321
+ schema:
322
+ type: object
323
+ properties:
324
+ flag: { type: boolean }
325
+ overwrite:
326
+ $ref: "#/components/schemas/OverwriteInput"
327
+ confirmed_edit:
328
+ type: boolean
329
+ description: "ADMIN-ONLY review verdict — true makes the standing overwrite effective."
330
+ responses:
331
+ "200":
332
+ description: The updated result (derived overwrite ratio/score stamped; effective `score`/`answer`/`ratio` recomputed per `confirmed_edit`).
333
+ content:
334
+ application/json:
335
+ schema:
336
+ $ref: "#/components/schemas/MetricResult"
337
+ "400":
338
+ description: "Non-overwritable key, `ratio`/`score` sent, wrong value type, or `overwrite` not an object."
339
+ "403":
340
+ description: Rep clearing `flag`, sending `confirmed_edit`, or touching another rep's session.
341
+ "404":
342
+ description: Not found.
343
+ delete:
344
+ summary: Soft-delete a metric result (admin)
345
+ operationId: removeAiObjectDetectionMetricResult
346
+ parameters:
347
+ - in: path
348
+ name: id
349
+ required: true
350
+ schema: { type: string }
351
+ responses:
352
+ "200":
353
+ description: The disabled result.
354
+ content:
355
+ application/json:
356
+ schema:
357
+ $ref: "#/components/schemas/MetricResult"
358
+ "403":
359
+ description: Rep tokens may not remove results.
360
+ "404":
361
+ description: Not found.
362
+ components:
363
+ securitySchemes:
364
+ ApiKeyAuth:
365
+ type: apiKey
366
+ in: header
367
+ name: api-key
368
+ JwtAuth:
369
+ type: apiKey
370
+ in: header
371
+ name: Authorization
372
+ schemas:
373
+ UserStamp:
374
+ type: object
375
+ description: "Who created / last edited the document (server-stamped from the token)."
376
+ properties:
377
+ _id: { type: string }
378
+ name: { type: string }
379
+ type:
380
+ type: string
381
+ enum: [admin, rep, client, tenant]
382
+ admin: { type: string }
383
+ rep: { type: string }
384
+ client: { type: string }
385
+ tenant: { type: string }
386
+ AdjacentBlockComputed:
387
+ type: object
388
+ description: "Computed shape for `adjacent_block` (compatibility)."
389
+ properties:
390
+ type:
391
+ type: string
392
+ enum: [adjacent_block]
393
+ description: Internal discriminator mirror.
394
+ output:
395
+ type: string
396
+ enum: [compatibility]
397
+ description: Family tag stamped by the engine.
398
+ answer:
399
+ type: boolean
400
+ description: The verdict — contiguous AND count within [from, to].
401
+ score: { type: number, minimum: 0, maximum: 1 }
402
+ member_count: { type: number }
403
+ cut_count: { type: number }
404
+ in_range: { type: boolean }
405
+ blocks:
406
+ type: array
407
+ items:
408
+ type: object
409
+ properties:
410
+ shelf: { type: number }
411
+ from_stack: { type: number }
412
+ to_stack: { type: number }
413
+ count: { type: number }
414
+ FacingsCountComputed:
415
+ type: object
416
+ description: "Computed shape for `facings_count` (numerical)."
417
+ properties:
418
+ type:
419
+ type: string
420
+ enum: [facings_count]
421
+ output:
422
+ type: string
423
+ enum: [numerical]
424
+ description: Family tag stamped by the engine.
425
+ answer:
426
+ type: number
427
+ description: The collected facings count.
428
+ target_answer:
429
+ type: number
430
+ description: "Echo of the metric's optional target — present only when set; score then = answer ÷ target_answer (clamped)."
431
+ score: { type: number, minimum: 0, maximum: 1 }
432
+ shelves:
433
+ type: array
434
+ items:
435
+ type: object
436
+ properties:
437
+ shelf: { type: number }
438
+ count: { type: number }
439
+ OnShelfAvailabilityComputed:
440
+ type: object
441
+ description: "Computed shape for `on_shelf_availability` (numerical)."
442
+ properties:
443
+ type:
444
+ type: string
445
+ enum: [on_shelf_availability]
446
+ output:
447
+ type: string
448
+ enum: [numerical]
449
+ description: Family tag stamped by the engine.
450
+ answer:
451
+ type: number
452
+ description: "How many of the selected labels are AVAILABLE (≥ 1 facing)."
453
+ score:
454
+ type: number
455
+ minimum: 0
456
+ maximum: 1
457
+ description: "answer ÷ target_answer when a target is set, else `ratio` (the availability factor)."
458
+ target_answer:
459
+ type: number
460
+ description: "Echo of the metric's optional target — present only when set."
461
+ total:
462
+ type: number
463
+ description: How many labels were selected (the denominator).
464
+ ratio:
465
+ type: number
466
+ description: answer / total.
467
+ missing:
468
+ type: array
469
+ description: "The out-of-stock labels — the actionable list (names resolved at evaluation time)."
470
+ items:
471
+ type: object
472
+ properties:
473
+ label: { type: string }
474
+ name: { type: string }
475
+ present:
476
+ type: array
477
+ description: The available labels with their facing counts.
478
+ items:
479
+ type: object
480
+ properties:
481
+ label: { type: string }
482
+ name: { type: string }
483
+ facings: { type: number }
484
+ SegmentOutput:
485
+ type: object
486
+ description: |
487
+ One segment row's outcome inside a share-of-shelf evaluation.
488
+ Self-contained on purpose: embedded in `computed.segments[]` today
489
+ but ready to move to its own collection and to be reused by future
490
+ SOS-family metric types. Only the MAIN row carries `target_ratio`,
491
+ `target_answer` and `score`.
492
+ properties:
493
+ segment: { type: string }
494
+ name: { type: string }
495
+ resolved:
496
+ type: boolean
497
+ description: false = the segment was deleted after the metric referenced it.
498
+ main:
499
+ type: boolean
500
+ description: The row the metric's target is defined for — exactly one per metric.
501
+ labels:
502
+ type: array
503
+ items: { type: string }
504
+ description: The effective labels used (override or the segment's own).
505
+ answer:
506
+ type: number
507
+ description: Measured quantity in the metric's measure unit (cm or cm²).
508
+ overwrite_answer:
509
+ type: number
510
+ description: "The CONFIRMED answer override, colocated in the row (effective = overwrite_answer ?? answer) — present only while confirmed_edit is true. Source of truth is the result's `overwrite.answer`; the service keeps this copy in sync across override saves, confirmations and recalculations."
511
+ overwrite_ratio:
512
+ type: number
513
+ description: "The confirmed override's derived ratio, colocated with overwrite_answer (same lifecycle)."
514
+ overwrite_score:
515
+ type: number
516
+ description: "The confirmed override's derived score, colocated with overwrite_answer (same lifecycle)."
517
+ ratio:
518
+ type: number
519
+ description: answer / category total — rows sum to 1 when segments don't overlap.
520
+ target_ratio:
521
+ type: number
522
+ description: Main row only — the target share this segment must reach.
523
+ target_answer:
524
+ type: number
525
+ description: "Main row only — target_ratio × total: the quantity needed to hit the target."
526
+ score:
527
+ type: number
528
+ minimum: 0
529
+ maximum: 1
530
+ description: "Main row only — attainment vs the target (share_of_shelf: min(1, ratio / target_ratio)). Missions multiply it by their weight."
531
+ ShareOfShelfComputed:
532
+ type: object
533
+ description: "Computed shape for `share_of_shelf` — the MAIN segment's numbers at the top level, every row in `segments[]`."
534
+ properties:
535
+ type:
536
+ type: string
537
+ enum: [share_of_shelf]
538
+ output:
539
+ type: string
540
+ enum: [share_of_shelf]
541
+ description: Family tag stamped by the engine.
542
+ answer:
543
+ type: number
544
+ nullable: true
545
+ description: "The MAIN segment's measured quantity (cm or cm²); null when nothing was measurable."
546
+ score:
547
+ type: number
548
+ minimum: 0
549
+ maximum: 1
550
+ description: "= the main segment's score: min(1, ratio / target_ratio)."
551
+ total:
552
+ type: number
553
+ description: "The same measure over the CONSIDERED CATEGORY: facings whose label belongs to any of the metric's segments."
554
+ ratio:
555
+ type: number
556
+ description: answer / total — the main segment's share.
557
+ target_ratio:
558
+ type: number
559
+ description: The target the MAIN segment must meet (from the metric args).
560
+ target_answer:
561
+ type: number
562
+ description: "target_ratio × total — what the main segment needed, in measure units."
563
+ measure:
564
+ type: string
565
+ enum: [width_cm, area_cm2, facings]
566
+ unmeasured:
567
+ type: number
568
+ description: Considered facings without a physical size (excluded from both sides).
569
+ segments:
570
+ type: array
571
+ description: One SegmentOutput per metric row (the main one flagged + scored).
572
+ items:
573
+ $ref: "#/components/schemas/SegmentOutput"
574
+ Overwrite:
575
+ type: object
576
+ description: "Stored human layer — the entered VALUE keys plus the server-DERIVED `ratio`/`score`, strict per type (plus the internal `type` mirror). Which keys exist depends on the type: adjacent_block `answer` (boolean) / `member_count` / `cut_count` / `score`; facings_count `answer` / `score`; on_shelf_availability `answer` / `ratio` / `score`; share_of_shelf `answer` / `total` / `ratio` / `score`."
577
+ properties:
578
+ type:
579
+ type: string
580
+ enum:
581
+ [
582
+ adjacent_block,
583
+ facings_count,
584
+ on_shelf_availability,
585
+ share_of_shelf,
586
+ ]
587
+ answer:
588
+ description: boolean for adjacent_block, number otherwise.
589
+ oneOf:
590
+ - type: boolean
591
+ - type: number
592
+ member_count: { type: number }
593
+ cut_count: { type: number }
594
+ total: { type: number }
595
+ ratio: { type: number }
596
+ score: { type: number, minimum: 0, maximum: 1 }
597
+ OverwriteInput:
598
+ type: object
599
+ description: "Sparse VALUE overrides — only the type's overwritable keys; `ratio`/`score` are rejected (derived). Send `{}` to clear the override."
600
+ properties:
601
+ answer:
602
+ description: "adjacent_block: boolean verdict; other types: number."
603
+ oneOf:
604
+ - type: boolean
605
+ - type: number
606
+ member_count:
607
+ type: number
608
+ description: adjacent_block only.
609
+ cut_count:
610
+ type: number
611
+ description: adjacent_block only.
612
+ total:
613
+ type: number
614
+ description: share_of_shelf only.
615
+ MetricResult:
616
+ type: object
617
+ properties:
618
+ _id: { type: string }
619
+ disabled: { type: boolean }
620
+ metric:
621
+ type: string
622
+ description: "Metric id (or the populated metric document when `populatedKeys` includes `metric`)."
623
+ analysis:
624
+ type: string
625
+ description: "Analysis id (or the populated document when requested)."
626
+ session:
627
+ type: string
628
+ description: "Session id (or the populated document when requested)."
629
+ name:
630
+ type: string
631
+ description: Metric name snapshot at evaluation time.
632
+ type:
633
+ type: string
634
+ enum:
635
+ [
636
+ adjacent_block,
637
+ facings_count,
638
+ on_shelf_availability,
639
+ share_of_shelf,
640
+ ]
641
+ output:
642
+ type: string
643
+ enum: [compatibility, numerical, share_of_shelf]
644
+ description: The type's output family (denormalized for rendering).
645
+ args:
646
+ type: object
647
+ description: Args snapshot at evaluation time.
648
+ computed:
649
+ description: "Machine layer — strict per-type engine output incl. `answer` + `score` 0..1 (plus an internal `type` mirror). Never editable. Absent when the evaluation errored."
650
+ oneOf:
651
+ - $ref: "#/components/schemas/AdjacentBlockComputed"
652
+ - $ref: "#/components/schemas/FacingsCountComputed"
653
+ - $ref: "#/components/schemas/OnShelfAvailabilityComputed"
654
+ - $ref: "#/components/schemas/ShareOfShelfComputed"
655
+ user:
656
+ type: string
657
+ nullable: true
658
+ description: "Denormalized scan context (activity-model shape): the SCANNING rep's id — unset for admin-scanned sessions. Stamped by the evaluator; feeds the dashboard widgets' rep_id filter."
659
+ user_name: { type: string, nullable: true }
660
+ client:
661
+ type: string
662
+ nullable: true
663
+ description: "The scanned client's id (widget client.* filters inflate it in their header stages)."
664
+ client_name: { type: string, nullable: true }
665
+ teams:
666
+ type: array
667
+ items: { type: string }
668
+ description: "The scanning rep's team ids at evaluation time (team-shared dashboards)."
669
+ visit_id:
670
+ type: string
671
+ description: Device visit id of the visit the scan happened in (copied from the session).
672
+ route:
673
+ type: string
674
+ description: "`sv.routes` `_id` of the visit's route."
675
+ business_day:
676
+ type: string
677
+ description: "Business day of the scan, `YYYY-MM-DD`."
678
+ overwrite:
679
+ $ref: "#/components/schemas/Overwrite"
680
+ flag: { type: boolean }
681
+ confirmed_edit:
682
+ type: boolean
683
+ description: "Admin review gate — the overwrite only drives the effective score while true. A fresh overwrite resets it to false; reps can never set it."
684
+ score:
685
+ type: number
686
+ description: "EFFECTIVE score 0..1 (confirmed `overwrite.score`, else `computed.score`)."
687
+ answer:
688
+ description: "EFFECTIVE answer, reconciled like `score` (confirmed override wins) — boolean verdict or number per the output family. What widgets/analytics aggregate."
689
+ nullable: true
690
+ oneOf:
691
+ - type: boolean
692
+ - type: number
693
+ ratio:
694
+ type: number
695
+ nullable: true
696
+ description: "EFFECTIVE ratio, reconciled like `score` (OSA availability / SOS share; null for types without one)."
697
+ status:
698
+ type: string
699
+ enum: [ok, error]
700
+ error: { type: string, nullable: true }
701
+ evaluated_at:
702
+ type: number
703
+ description: Unix ms of the evaluation.
704
+ engine_version: { type: number }
705
+ source_fingerprints:
706
+ type: object
707
+ description: The analysis's stage fingerprints at evaluation time — mismatch vs the analysis's current ones = stale result.
708
+ creator:
709
+ $ref: "#/components/schemas/UserStamp"
710
+ editor:
711
+ $ref: "#/components/schemas/UserStamp"
712
+ company_namespace:
713
+ type: array
714
+ items: { type: string }
715
+ createdAt: { type: string, format: date-time }
716
+ updatedAt: { type: string, format: date-time }
717
+ MetricResultFindResult:
718
+ type: object
719
+ description: Standard paginated result envelope.
720
+ properties:
721
+ data:
722
+ type: array
723
+ items:
724
+ $ref: "#/components/schemas/MetricResult"
725
+ total_result: { type: number }
726
+ current_count: { type: number }
727
+ total_pages: { type: number }
728
+ current_page: { type: number }
729
+ per_page: { type: number }
730
+ first_page_url: { type: string }
731
+ last_page_url: { type: string }
732
+ next_page_url: { type: string, nullable: true }
733
+ prev_page_url: { type: string, nullable: true }
734
+ path: { type: string }