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
@@ -3,7 +3,7 @@ import { SalesAnalyticsFieldAccumulators, SalesAnalyticsReportProjectionKey, Sal
3
3
  export interface Params {
4
4
  [key: string]: any;
5
5
  }
6
- export type ReportType = "sales-analytics" | "retail-execution-gallery" | "from-v2-gallery";
6
+ export type ReportType = "sales-analytics" | "retail-execution-gallery" | "from-v2-gallery" | "object-detection-metrics" | "object-detection-segments";
7
7
  export interface Data {
8
8
  [key: string]: any;
9
9
  }
@@ -20611,6 +20611,4535 @@ export declare namespace Service {
20611
20611
  }
20612
20612
  export {};
20613
20613
  }
20614
+ namespace AiObjectDetectionDataset {
20615
+ interface Data {
20616
+ _id: StringId;
20617
+ /** Unique per namespace (compound unique index on `company_namespace` + `name`). */
20618
+ name: string;
20619
+ /** `ai-object-detection-label` ids that make up the dataset. */
20620
+ dataset_labels: StringId[];
20621
+ /** `ai-object-detection-model` used by default when training/predicting on this dataset. */
20622
+ default_model?: StringId;
20623
+ /** Pinned `ai-object-detection-model-version`. `null`/absent = "latest" → resolved to the model's `current_model_version` at inference time. */
20624
+ default_model_version?: StringId | null;
20625
+ disabled: boolean;
20626
+ company_namespace: string[];
20627
+ createdAt: Date;
20628
+ updatedAt: Date;
20629
+ }
20630
+ type PopulatedKeys = "dataset_labels" | "default_model" | "default_model_version";
20631
+ /** Populated refs are emitted under `<field>_populated`; the original field keeps the id(s). */
20632
+ interface DataWithPopulatedKeys extends Data {
20633
+ dataset_labels_populated?: AiObjectDetectionLabel.Data[];
20634
+ default_model_populated?: AiObjectDetectionModel.Data | null;
20635
+ default_model_version_populated?: AiObjectDetectionModelVersion.Data | null;
20636
+ }
20637
+ interface CreateBody {
20638
+ name: string;
20639
+ dataset_labels: StringId[];
20640
+ default_model?: StringId;
20641
+ /** Version `_id` to pin, or `"latest"` / `""` / `null` to track the model's `current_model_version` (the sentinel is normalized to `null` server-side, never cast to an ObjectId). */
20642
+ default_model_version?: StringId | "latest" | null;
20643
+ company_namespace?: string[];
20644
+ }
20645
+ interface UpdateBody {
20646
+ name?: string;
20647
+ dataset_labels?: StringId[];
20648
+ default_model?: StringId;
20649
+ /** Send `"latest"` / `""` / `null` to actively CLEAR a previously pinned version. */
20650
+ default_model_version?: StringId | "latest" | null;
20651
+ /** Set `true` to soft-delete. */
20652
+ disabled?: boolean;
20653
+ }
20654
+ namespace Find {
20655
+ type Params = DefaultPaginationQueryParams & {
20656
+ _id?: StringId | StringId[];
20657
+ name?: string | string[];
20658
+ /** Datasets containing any of the given label ids. */
20659
+ dataset_labels?: StringId | StringId[];
20660
+ default_model?: StringId | StringId[];
20661
+ default_model_version?: StringId | StringId[];
20662
+ /** Case-insensitive regex match on `name`. */
20663
+ search?: string;
20664
+ disabled?: boolean;
20665
+ from_updatedAt?: number;
20666
+ to_updatedAt?: number;
20667
+ from_createdAt?: number;
20668
+ to_createdAt?: number;
20669
+ populatedKeys?: PopulatedKeys[];
20670
+ };
20671
+ interface Result extends DefaultPaginationResult {
20672
+ data: DataWithPopulatedKeys[];
20673
+ }
20674
+ }
20675
+ namespace Get {
20676
+ type ID = StringId;
20677
+ interface Params {
20678
+ populatedKeys?: PopulatedKeys[];
20679
+ }
20680
+ type Result = DataWithPopulatedKeys;
20681
+ }
20682
+ namespace Create {
20683
+ type Body = CreateBody;
20684
+ type Result = Data;
20685
+ }
20686
+ namespace Update {
20687
+ type ID = StringId;
20688
+ type Body = UpdateBody;
20689
+ type Result = Data;
20690
+ }
20691
+ namespace Remove {
20692
+ type ID = StringId;
20693
+ type Result = Data;
20694
+ }
20695
+ }
20696
+ namespace AiObjectDetectionModel {
20697
+ /** Free-form Ultralytics train args; the HUB endpoint reads `train_settings[0].epochs` / `.imgsz`. */
20698
+ interface TrainSettings {
20699
+ epochs?: number;
20700
+ imgsz?: number;
20701
+ batch?: number;
20702
+ [key: string]: any;
20703
+ }
20704
+ /** Free-form predict args; inference reads `predict_settings[0].conf` / `.iou` / `.agnostic_nms` as defaults. */
20705
+ interface PredictSettings {
20706
+ conf?: number;
20707
+ iou?: number;
20708
+ agnostic_nms?: boolean;
20709
+ [key: string]: any;
20710
+ }
20711
+ interface Data {
20712
+ _id: StringId;
20713
+ /** Must match `^[a-zA-Z_][a-zA-Z0-9_\s]*$`; unique per namespace. */
20714
+ name: string;
20715
+ train_settings?: TrainSettings[];
20716
+ predict_settings?: PredictSettings[];
20717
+ /** Resolution target for every unpinned ("latest") reference. Auto-advanced (forward-only) to the newest version whose weights upload completes. */
20718
+ current_model_version?: StringId;
20719
+ /** Validation split ratio, exclusive range (0.05, 0.4). Default 0.25. */
20720
+ validation_size?: number;
20721
+ /** Test split ratio, exclusive range (0, 0.2). Default 0.05. */
20722
+ test_size?: number;
20723
+ disabled: boolean;
20724
+ company_namespace: string[];
20725
+ createdAt: Date;
20726
+ updatedAt: Date;
20727
+ }
20728
+ type PopulatedKeys = "current_model_version";
20729
+ /** The version document with its weight / train-data / confusion-matrix media refs populated in place (no `_populated` suffix on the nested keys). */
20730
+ type PopulatedCurrentModelVersion = Omit<AiObjectDetectionModelVersion.Data, "weight_best" | "weight_last" | "train_data" | "confusion_matrix" | "confusion_matrix_normalized"> & {
20731
+ weight_best?: MediaStorage.MediaStorageSchema | null;
20732
+ weight_last?: MediaStorage.MediaStorageSchema | null;
20733
+ train_data?: MediaStorage.MediaStorageSchema | null;
20734
+ confusion_matrix?: MediaStorage.MediaStorageSchema | null;
20735
+ confusion_matrix_normalized?: MediaStorage.MediaStorageSchema | null;
20736
+ };
20737
+ interface DataWithPopulatedKeys extends Data {
20738
+ current_model_version_populated?: PopulatedCurrentModelVersion | null;
20739
+ }
20740
+ interface CreateBody {
20741
+ name: string;
20742
+ train_settings?: TrainSettings[];
20743
+ predict_settings?: PredictSettings[];
20744
+ /** Normally left unset — the server advances it when a version finishes training. */
20745
+ current_model_version?: StringId;
20746
+ validation_size?: number;
20747
+ test_size?: number;
20748
+ company_namespace?: string[];
20749
+ }
20750
+ type UpdateBody = Partial<Omit<Data, "_id" | "createdAt" | "updatedAt" | "company_namespace">>;
20751
+ namespace Find {
20752
+ type Params = DefaultPaginationQueryParams & {
20753
+ _id?: StringId | StringId[];
20754
+ name?: string | string[];
20755
+ current_model_version?: StringId | StringId[];
20756
+ /** Case-insensitive regex match on `name`. */
20757
+ search?: string;
20758
+ disabled?: boolean;
20759
+ from_updatedAt?: number;
20760
+ to_updatedAt?: number;
20761
+ from_createdAt?: number;
20762
+ to_createdAt?: number;
20763
+ populatedKeys?: PopulatedKeys[];
20764
+ };
20765
+ interface Result extends DefaultPaginationResult {
20766
+ data: DataWithPopulatedKeys[];
20767
+ }
20768
+ }
20769
+ namespace Get {
20770
+ type ID = StringId;
20771
+ interface Params {
20772
+ populatedKeys?: PopulatedKeys[];
20773
+ }
20774
+ type Result = DataWithPopulatedKeys;
20775
+ }
20776
+ namespace Create {
20777
+ type Body = CreateBody;
20778
+ type Result = Data;
20779
+ }
20780
+ namespace Update {
20781
+ type ID = StringId;
20782
+ type Body = UpdateBody;
20783
+ type Result = Data;
20784
+ }
20785
+ namespace Remove {
20786
+ type ID = StringId;
20787
+ type Result = Data;
20788
+ }
20789
+ }
20790
+ namespace AiObjectDetectionModelVersion {
20791
+ /** Lifecycle of the async dataset-prep job, then `trained` once `ul-hub` receives the weights. */
20792
+ type Status = "initiated" | "setting_alarm_completed" | "building_folder_in_progress" | "building_folder_failed" | "building_yaml_file_in_progress" | "building_yaml_file_completed" | "stream_tasks_in_progress" | "stream_tasks_failed" | "compress_folder_in_progress" | "compress_folder_failed" | "uploading_zip_to_s3_in_progress" | "uploading_zip_to_s3_failed" | "zip_file_completed" | "trained";
20793
+ /** `smart` = only `manual` / `auto_edited` annotation groups; `all` also includes untouched `auto` groups. */
20794
+ type TaskSelectionMode = "smart" | "all";
20795
+ /** Creator stamp taken from the JWT; only admins create versions. */
20796
+ interface Creator {
20797
+ _id: StringId;
20798
+ type: "admin";
20799
+ admin?: StringId;
20800
+ name?: string;
20801
+ }
20802
+ /** Per-label box count written into the YOLO label files (class balance of the export). */
20803
+ interface AnnotationPerLabel {
20804
+ label_id: StringId;
20805
+ name: string;
20806
+ size?: number;
20807
+ }
20808
+ /** Free-form Ultralytics train args; the HUB endpoint reads `train_settings[0].epochs` / `.imgsz`. */
20809
+ interface TrainSettings {
20810
+ epochs?: number;
20811
+ imgsz?: number;
20812
+ batch?: number;
20813
+ [key: string]: any;
20814
+ }
20815
+ /** Best-epoch (highest mAP50-95, the epoch `best.pt` was saved from) roll-up snapshotted when training completes. */
20816
+ interface Metrics {
20817
+ mAP50?: number;
20818
+ mAP50_95?: number;
20819
+ precision?: number;
20820
+ recall?: number;
20821
+ /** `_index` of the best epoch. */
20822
+ best_epoch?: number;
20823
+ epochs_reported?: number;
20824
+ }
20825
+ /** Architecture cost as Ultralytics reports it. */
20826
+ interface ModelStats {
20827
+ parameters?: number;
20828
+ GFLOPs?: number;
20829
+ speed_PyTorch_ms?: number;
20830
+ }
20831
+ interface Data {
20832
+ _id: StringId;
20833
+ /** Server-assigned, 1-based, increments per model within the namespace. */
20834
+ version_code: number;
20835
+ model: StringId;
20836
+ dataset: StringId[];
20837
+ /** Server-derived: union of the selected datasets' `dataset_labels`. */
20838
+ dataset_labels: StringId[];
20839
+ status?: Status;
20840
+ /** Reserved flag; the split logic currently clamps shortfalls at 0 regardless. Default `false`. */
20841
+ suppress_exceeding_sizes: boolean;
20842
+ /** Media id of the YOLO dataset zip produced by the prep job. */
20843
+ train_data?: StringId;
20844
+ creator: Creator;
20845
+ task_selection_mode: TaskSelectionMode;
20846
+ /** Tasks that produced an image + label file in the zip. */
20847
+ tasks_size?: number;
20848
+ validation_size?: number;
20849
+ test_size?: number;
20850
+ train_settings?: TrainSettings[];
20851
+ /** Sorted by `size` descending. */
20852
+ annotations_per_label?: AnnotationPerLabel[];
20853
+ /** Boxes written into the label files. */
20854
+ annotations_size?: number;
20855
+ /** Training artifacts uploaded after the run (media ids). */
20856
+ confusion_matrix_normalized?: StringId;
20857
+ /** Media id of the run's `args.yaml`. */
20858
+ args?: StringId;
20859
+ confusion_matrix?: StringId;
20860
+ F1_curve?: StringId;
20861
+ labels_correlogram?: StringId;
20862
+ labels?: StringId;
20863
+ P_curve?: StringId;
20864
+ PR_curve?: StringId;
20865
+ R_curve?: StringId;
20866
+ results_csv?: StringId;
20867
+ results?: StringId;
20868
+ val_batch0_labels?: StringId;
20869
+ val_batch0_pred?: StringId;
20870
+ /** Base weights the run starts from (e.g. `yolov8n.pt` or a weights URL). */
20871
+ initial_weight: string;
20872
+ /** Media id of `best.pt` — set by the `ul-hub` weights upload. */
20873
+ weight_best?: StringId;
20874
+ /** Media id of `last.pt`. */
20875
+ weight_last?: StringId;
20876
+ metrics?: Metrics;
20877
+ model_stats?: ModelStats;
20878
+ disabled: boolean;
20879
+ /** Adapted errors pushed by the async prep pipeline. */
20880
+ _errors?: any[];
20881
+ company_namespace: string[];
20882
+ createdAt: Date;
20883
+ updatedAt: Date;
20884
+ }
20885
+ type PopulatedKeys = "model" | "train_data" | "dataset" | "dataset_labels" | "confusion_matrix_normalized" | "args" | "confusion_matrix" | "F1_curve" | "labels_correlogram" | "labels" | "P_curve" | "PR_curve" | "R_curve" | "results_csv" | "results" | "val_batch0_labels" | "val_batch0_pred" | "weight_best" | "weight_last";
20886
+ /** Populated refs are emitted under `<field>_populated`; the original field keeps the id(s). */
20887
+ interface DataWithPopulatedKeys extends Data {
20888
+ model_populated?: AiObjectDetectionModel.Data | null;
20889
+ dataset_populated?: AiObjectDetectionDataset.Data[];
20890
+ dataset_labels_populated?: AiObjectDetectionLabel.Data[];
20891
+ train_data_populated?: MediaStorage.MediaStorageSchema | null;
20892
+ confusion_matrix_normalized_populated?: MediaStorage.MediaStorageSchema | null;
20893
+ args_populated?: MediaStorage.MediaStorageSchema | null;
20894
+ confusion_matrix_populated?: MediaStorage.MediaStorageSchema | null;
20895
+ F1_curve_populated?: MediaStorage.MediaStorageSchema | null;
20896
+ labels_correlogram_populated?: MediaStorage.MediaStorageSchema | null;
20897
+ labels_populated?: MediaStorage.MediaStorageSchema | null;
20898
+ P_curve_populated?: MediaStorage.MediaStorageSchema | null;
20899
+ PR_curve_populated?: MediaStorage.MediaStorageSchema | null;
20900
+ R_curve_populated?: MediaStorage.MediaStorageSchema | null;
20901
+ results_csv_populated?: MediaStorage.MediaStorageSchema | null;
20902
+ results_populated?: MediaStorage.MediaStorageSchema | null;
20903
+ val_batch0_labels_populated?: MediaStorage.MediaStorageSchema | null;
20904
+ val_batch0_pred_populated?: MediaStorage.MediaStorageSchema | null;
20905
+ weight_best_populated?: MediaStorage.MediaStorageSchema | null;
20906
+ weight_last_populated?: MediaStorage.MediaStorageSchema | null;
20907
+ }
20908
+ interface CreateBody {
20909
+ /** `ai-object-detection-model` id (must exist in the namespace). */
20910
+ model: StringId;
20911
+ /** One or more `ai-object-detection-dataset` ids. */
20912
+ dataset: StringId[];
20913
+ /** Ignored — the server recomputes it as the union of the datasets' `dataset_labels`. */
20914
+ dataset_labels?: StringId[];
20915
+ initial_weight: string;
20916
+ /** Default `all`. */
20917
+ task_selection_mode?: TaskSelectionMode;
20918
+ suppress_exceeding_sizes?: boolean;
20919
+ train_settings?: TrainSettings[];
20920
+ validation_size?: number;
20921
+ test_size?: number;
20922
+ company_namespace?: string[];
20923
+ }
20924
+ /** Label projection returned by `create` (`select: ["name"]`). */
20925
+ interface LabelRef {
20926
+ _id: StringId;
20927
+ name: string;
20928
+ }
20929
+ /** `create` returns the new version with `dataset`, `model` and `dataset_labels` populated in place (status is still `initiated` in the response). */
20930
+ type CreateResult = Omit<Data, "dataset" | "model" | "dataset_labels"> & {
20931
+ dataset: AiObjectDetectionDataset.Data[];
20932
+ model: AiObjectDetectionModel.Data;
20933
+ dataset_labels: LabelRef[];
20934
+ };
20935
+ namespace Find {
20936
+ type Params = DefaultPaginationQueryParams & {
20937
+ _id?: StringId | StringId[];
20938
+ /** Accepted by the backend but the schema has no `name` field — effectively a no-op. */
20939
+ name?: string | string[];
20940
+ model?: StringId | StringId[];
20941
+ version_code?: number | number[];
20942
+ /** Versions pinning any of the given dataset ids. */
20943
+ dataset?: StringId | StringId[];
20944
+ dataset_labels?: StringId | StringId[];
20945
+ /** Regex on `name` — no-op for this schema (see `name`). */
20946
+ search?: string;
20947
+ disabled?: boolean;
20948
+ from_updatedAt?: number;
20949
+ to_updatedAt?: number;
20950
+ from_createdAt?: number;
20951
+ to_createdAt?: number;
20952
+ populatedKeys?: PopulatedKeys[];
20953
+ };
20954
+ interface Result extends DefaultPaginationResult {
20955
+ data: DataWithPopulatedKeys[];
20956
+ }
20957
+ }
20958
+ namespace Get {
20959
+ type ID = StringId;
20960
+ interface Params {
20961
+ populatedKeys?: PopulatedKeys[];
20962
+ }
20963
+ type Result = DataWithPopulatedKeys;
20964
+ }
20965
+ namespace Create {
20966
+ type Body = CreateBody;
20967
+ type Result = CreateResult;
20968
+ }
20969
+ namespace Remove {
20970
+ type ID = StringId;
20971
+ type Result = Data;
20972
+ }
20973
+ }
20974
+ namespace AiObjectDetectionModelVersionEpoch {
20975
+ /** Ultralytics loss components for one epoch. */
20976
+ interface LossMetrics {
20977
+ /** Bounding-box regression loss. */
20978
+ box_loss?: number;
20979
+ /** Classification loss. */
20980
+ cls_loss?: number;
20981
+ /** Distribution focal loss. */
20982
+ dfl_loss?: number;
20983
+ }
20984
+ /** Validation metrics for one epoch. The `_B` suffix is Ultralytics' own — the Box (detection) task. */
20985
+ interface PerformanceMetrics {
20986
+ precision_B?: number;
20987
+ recall_B?: number;
20988
+ /** mAP at IoU 0.50 — the lenient, headline number. */
20989
+ mAP50_B?: number;
20990
+ /** mAP averaged over IoU 0.50:0.95 — the strict number `best.pt` is selected on. */
20991
+ mAP50_95_B?: number;
20992
+ }
20993
+ /** Architecture cost; Ultralytics reports it only on some epochs. */
20994
+ interface ModelStats {
20995
+ /** Total weight count. */
20996
+ parameters?: number;
20997
+ /** Forward-pass cost per image. */
20998
+ GFLOPs?: number;
20999
+ /** Per-image PyTorch inference latency measured during validation. */
21000
+ speed_PyTorch_ms?: number;
21001
+ }
21002
+ /** One training epoch as ingested by `ul-hub-v1-models` (keys de-slashed: `train/box_loss` → `train_loss_metrics.box_loss`). */
21003
+ interface Data {
21004
+ _id: StringId;
21005
+ /** 0-based epoch number; unique per `model_version`. */
21006
+ _index: number;
21007
+ model_version: StringId;
21008
+ /** Payload kind reported by the trainer. Only `metrics` today. */
21009
+ type: "metrics";
21010
+ train_loss_metrics?: LossMetrics;
21011
+ val_loss_metrics?: LossMetrics;
21012
+ performance_metrics?: PerformanceMetrics;
21013
+ model_stats?: ModelStats;
21014
+ disabled: boolean;
21015
+ company_namespace: string[];
21016
+ createdAt: Date;
21017
+ updatedAt: Date;
21018
+ }
21019
+ interface PerformanceRollup {
21020
+ mAP50?: number;
21021
+ mAP50_95?: number;
21022
+ precision?: number;
21023
+ recall?: number;
21024
+ }
21025
+ /** Derived on read from the returned series. */
21026
+ interface Summary {
21027
+ /** Epochs reported so far — less than the configured `epochs` while a run is in flight. */
21028
+ epochs_reported: number;
21029
+ /** `_index` of the highest-mAP50-95 epoch — what `best.pt` holds. */
21030
+ best_epoch?: number;
21031
+ best?: PerformanceRollup;
21032
+ /** Last reported epoch — diverges from `best` when the run overfit. */
21033
+ final?: PerformanceRollup;
21034
+ /** Carried on the last epoch that reported it. */
21035
+ model_stats?: ModelStats;
21036
+ /** val − train (box+cls+dfl) at the final epoch; positive and growing = overfitting. */
21037
+ final_generalization_gap?: number;
21038
+ }
21039
+ namespace Find {
21040
+ /** Not paginated — `per_page` / `page` / `sort` are ignored; the whole run is returned in `_index` order. */
21041
+ interface Params {
21042
+ /** Required — 400 without it. */
21043
+ model_version: StringId | StringId[];
21044
+ _id?: StringId | StringId[];
21045
+ /** Filter to specific epoch index(es). */
21046
+ _index?: number | number[];
21047
+ type?: "metrics";
21048
+ disabled?: boolean;
21049
+ }
21050
+ interface Result {
21051
+ /** Every reported epoch, ascending by `_index`. */
21052
+ data: Data[];
21053
+ total_result: number;
21054
+ summary: Summary;
21055
+ }
21056
+ }
21057
+ namespace Get {
21058
+ type ID = StringId;
21059
+ type Result = Data;
21060
+ }
21061
+ }
21062
+ namespace AiObjectDetectionModelVersionTrainAgent {
21063
+ interface Step {
21064
+ /** A ready-to-run shell / Python snippet (may span multiple lines). */
21065
+ code_message: string;
21066
+ }
21067
+ /** The bootstrap script for training one model version on a self-hosted agent. */
21068
+ interface Data {
21069
+ /** Ordered: (1) pin-install ultralytics, (2) point the HUB client at Repzo and train, (3) upload the run's plots + `args.yaml`. */
21070
+ steps: Step[];
21071
+ /** The pinned ultralytics release the snippet installs (`8.4.114`, the last one shipping `ultralytics.hub`). */
21072
+ ultralytics_version?: string;
21073
+ }
21074
+ namespace Get {
21075
+ /** The `ai-object-detection-model-version` id to generate the snippet for. */
21076
+ type ID = StringId;
21077
+ type Result = Data;
21078
+ }
21079
+ }
21080
+ namespace AiObjectDetectionLabel {
21081
+ /** Expected physical front-face size of the labelled item, in centimetres (each axis 0.1–500). */
21082
+ interface PhysicalSize {
21083
+ w_cm?: number;
21084
+ h_cm?: number;
21085
+ }
21086
+ interface Data {
21087
+ _id: StringId;
21088
+ /** Unique per namespace. Must match `^[a-zA-Z_][a-zA-Z0-9_\s]*$`. */
21089
+ name: string;
21090
+ disabled: boolean;
21091
+ /** Reference crop (media-storage id). Linked to the label on create/update so the upload is not treated as orphaned media. */
21092
+ media_photo?: StringId;
21093
+ variants?: StringId[];
21094
+ products?: StringId[];
21095
+ product_categories?: StringId[];
21096
+ product_subcategories?: StringId[];
21097
+ product_brands?: StringId[];
21098
+ product_groups?: StringId[];
21099
+ /** Annotation hot-key: exactly one lower-case letter or digit, excluding `i`, `o` and `_`. */
21100
+ keyboard_shortcut?: string;
21101
+ /** Sibling family for the dims reclassifier — detections are only re-labelled BETWEEN labels sharing a group. */
21102
+ label_group?: StringId;
21103
+ /** Optional; labels without dims are skipped by the reclassifier. Set manually or via the label-report `mode=dims` analyzer. */
21104
+ physical_size?: PhysicalSize;
21105
+ company_namespace: string[];
21106
+ createdAt: Date;
21107
+ updatedAt: Date;
21108
+ }
21109
+ type PopulatedKeys = "label_group" | "media_photo" | "variants" | "products" | "product_categories" | "product_subcategories" | "product_brands" | "product_groups";
21110
+ interface DataWithPopulatedKeys extends Data {
21111
+ label_group_populated?: AiObjectDetectionLabelGroup.Data;
21112
+ media_photo_populated?: MediaStorage.MediaStorageSchema;
21113
+ variants_populated?: Variant.VariantSchema[];
21114
+ products_populated?: Product.ProductSchema[];
21115
+ product_categories_populated?: Category.CategorySchema[];
21116
+ product_subcategories_populated?: SubCategory.SubCategorySchema[];
21117
+ product_brands_populated?: Brand.BrandSchema[];
21118
+ product_groups_populated?: ProductGroup.ProductGroupSchema[];
21119
+ }
21120
+ interface CreateBody {
21121
+ name: string;
21122
+ media_photo?: StringId;
21123
+ variants?: StringId[];
21124
+ products?: StringId[];
21125
+ product_categories?: StringId[];
21126
+ product_subcategories?: StringId[];
21127
+ product_brands?: StringId[];
21128
+ product_groups?: StringId[];
21129
+ keyboard_shortcut?: string;
21130
+ label_group?: StringId;
21131
+ physical_size?: PhysicalSize;
21132
+ company_namespace?: string[];
21133
+ }
21134
+ type UpdateBody = Partial<Omit<Data, "_id" | "createdAt" | "updatedAt" | "company_namespace">>;
21135
+ namespace Find {
21136
+ type Params = DefaultPaginationQueryParams & {
21137
+ _id?: StringId | StringId[];
21138
+ /** Exact match (single value or list). Use `search` for a substring match. */
21139
+ name?: string | string[];
21140
+ /** Case-insensitive regex on `name`. */
21141
+ search?: string;
21142
+ disabled?: boolean;
21143
+ label_group?: StringId | StringId[];
21144
+ variants?: StringId | StringId[];
21145
+ products?: StringId | StringId[];
21146
+ product_categories?: StringId | StringId[];
21147
+ product_subcategories?: StringId | StringId[];
21148
+ product_brands?: StringId | StringId[];
21149
+ product_groups?: StringId | StringId[];
21150
+ from_updatedAt?: number;
21151
+ to_updatedAt?: number;
21152
+ from_createdAt?: number;
21153
+ to_createdAt?: number;
21154
+ populatedKeys?: PopulatedKeys[];
21155
+ };
21156
+ interface Result extends DefaultPaginationResult {
21157
+ data: DataWithPopulatedKeys[];
21158
+ }
21159
+ }
21160
+ namespace Get {
21161
+ type ID = StringId;
21162
+ interface Params {
21163
+ populatedKeys?: PopulatedKeys[];
21164
+ }
21165
+ type Result = DataWithPopulatedKeys;
21166
+ }
21167
+ namespace Create {
21168
+ type Body = CreateBody;
21169
+ type Result = Data;
21170
+ }
21171
+ namespace Update {
21172
+ type ID = StringId;
21173
+ type Body = UpdateBody;
21174
+ type Result = Data;
21175
+ }
21176
+ namespace Remove {
21177
+ type ID = StringId;
21178
+ /** The label after soft-deletion (`disabled: true`). Rejected while any dataset still lists the label. */
21179
+ type Result = Data;
21180
+ }
21181
+ }
21182
+ namespace AiObjectDetectionLabelGroup {
21183
+ /**
21184
+ * A family of sibling labels the detector confuses (size / flavour variants of one
21185
+ * product line). The dims reclassifier only ever moves a detection BETWEEN labels of
21186
+ * one group. Labels join a group through `AiObjectDetectionLabel.Data.label_group`.
21187
+ */
21188
+ interface Data {
21189
+ _id: StringId;
21190
+ /** Unique per namespace among non-deleted groups. */
21191
+ name: string;
21192
+ disabled: boolean;
21193
+ company_namespace: string[];
21194
+ createdAt: Date;
21195
+ updatedAt: Date;
21196
+ }
21197
+ interface CreateBody {
21198
+ name: string;
21199
+ company_namespace?: string[];
21200
+ }
21201
+ type UpdateBody = Partial<Omit<Data, "_id" | "createdAt" | "updatedAt" | "company_namespace">>;
21202
+ namespace Find {
21203
+ type Params = DefaultPaginationQueryParams & {
21204
+ _id?: StringId | StringId[];
21205
+ /** Exact match (single value or list). Use `search` for a substring match. */
21206
+ name?: string | string[];
21207
+ /** Case-insensitive regex on `name`. */
21208
+ search?: string;
21209
+ disabled?: boolean;
21210
+ from_updatedAt?: number;
21211
+ to_updatedAt?: number;
21212
+ from_createdAt?: number;
21213
+ to_createdAt?: number;
21214
+ };
21215
+ interface Result extends DefaultPaginationResult {
21216
+ data: Data[];
21217
+ }
21218
+ }
21219
+ namespace Get {
21220
+ type ID = StringId;
21221
+ type Result = Data;
21222
+ }
21223
+ namespace Create {
21224
+ type Body = CreateBody;
21225
+ type Result = Data;
21226
+ }
21227
+ namespace Update {
21228
+ type ID = StringId;
21229
+ type Body = UpdateBody;
21230
+ type Result = Data;
21231
+ }
21232
+ namespace Remove {
21233
+ type ID = StringId;
21234
+ interface Params {
21235
+ /**
21236
+ * A group that still has live member labels is rejected (400, with
21237
+ * `{ assigned_labels, requires_force: true }`) unless `force: true`, which first
21238
+ * unsets `label_group` on those labels and then soft-deletes the group.
21239
+ */
21240
+ force?: boolean;
21241
+ }
21242
+ type Result = Data;
21243
+ }
21244
+ }
21245
+ namespace AiObjectDetectionCategory {
21246
+ /**
21247
+ * One AUTO-ANALYSIS recipe: which model / version to run and the full analyze-session
21248
+ * settings. A category with N settings produces N analyses per received session.
21249
+ */
21250
+ interface ModelSetting {
21251
+ /** Sub-document id (server-assigned). */
21252
+ _id?: StringId;
21253
+ /** `ai-object-detection-model` id. Always required — it resolves the "latest" version when `model_version` is unset. */
21254
+ model: StringId;
21255
+ /** Pinned `ai-object-detection-model-version` id. Unset = "latest" → resolved to `model.current_model_version` at analysis time. */
21256
+ model_version?: StringId;
21257
+ /** Analyze-session settings (SceneMathConfig + `conf`/`iou`, walk, `reclassify_*`, `size_gate*`, `build_point_cloud`, ...). Unset keys fall back to scene-math defaults. */
21258
+ config?: {
21259
+ [key: string]: any;
21260
+ };
21261
+ }
21262
+ /** Wire shape accepted on create/update: `model_version` may be the UI sentinel `"latest"`, `""` or `null`, all normalised to unset. */
21263
+ interface ModelSettingBody {
21264
+ _id?: StringId;
21265
+ model: StringId;
21266
+ model_version?: StringId | "latest" | null;
21267
+ config?: {
21268
+ [key: string]: any;
21269
+ };
21270
+ }
21271
+ /**
21272
+ * A detection CATEGORY groups what a capture session is ABOUT: the model settings to
21273
+ * auto-analyze it with, and the labels that matter for the jobs & metrics built on top.
21274
+ * The mobile app picks one before calibration; a session received WITH a category is
21275
+ * auto-analyzed once per `model_settings` item after its upload completes.
21276
+ */
21277
+ interface Data {
21278
+ _id: StringId;
21279
+ /** Unique per namespace among non-deleted categories. */
21280
+ name: string;
21281
+ model_settings: ModelSetting[];
21282
+ /** `ai-object-detection-label` ids this category tracks — inputs for later jobs/metrics. */
21283
+ labels: StringId[];
21284
+ disabled: boolean;
21285
+ company_namespace: string[];
21286
+ createdAt: Date;
21287
+ updatedAt: Date;
21288
+ }
21289
+ type PopulatedKeys = "labels" | "model_settings.model" | "model_settings.model_version";
21290
+ /** Nested populations are applied IN PLACE (mongoose `populate` on `model_settings.model[_version]`), not under a `_populated` key. */
21291
+ interface ModelSettingWithPopulatedKeys extends Omit<ModelSetting, "model" | "model_version"> {
21292
+ model: StringId | AiObjectDetectionModel.Data;
21293
+ model_version?: StringId | AiObjectDetectionModelVersion.Data;
21294
+ }
21295
+ interface DataWithPopulatedKeys extends Omit<Data, "model_settings"> {
21296
+ model_settings: ModelSettingWithPopulatedKeys[];
21297
+ labels_populated?: AiObjectDetectionLabel.Data[];
21298
+ }
21299
+ interface CreateBody {
21300
+ name: string;
21301
+ /** Each item must name a `model` (400 otherwise); `model_version` optional (absent = latest). */
21302
+ model_settings?: ModelSettingBody[];
21303
+ labels?: StringId[];
21304
+ company_namespace?: string[];
21305
+ }
21306
+ interface UpdateBody {
21307
+ name?: string;
21308
+ model_settings?: ModelSettingBody[];
21309
+ labels?: StringId[];
21310
+ /** Soft-delete flag — set `true` to disable via update (bypasses the session guard on remove). */
21311
+ disabled?: boolean;
21312
+ }
21313
+ namespace Find {
21314
+ type Params = DefaultPaginationQueryParams & {
21315
+ _id?: StringId | StringId[];
21316
+ /** Exact match (single value or list). Use `search` for a substring match. */
21317
+ name?: string | string[];
21318
+ /** Case-insensitive regex on `name`. */
21319
+ search?: string;
21320
+ /** Categories tracking any of the given label id(s). */
21321
+ labels?: StringId | StringId[];
21322
+ disabled?: boolean;
21323
+ from_updatedAt?: number;
21324
+ to_updatedAt?: number;
21325
+ from_createdAt?: number;
21326
+ to_createdAt?: number;
21327
+ populatedKeys?: PopulatedKeys[];
21328
+ };
21329
+ interface Result extends DefaultPaginationResult {
21330
+ data: DataWithPopulatedKeys[];
21331
+ }
21332
+ }
21333
+ namespace Get {
21334
+ type ID = StringId;
21335
+ type Result = Data;
21336
+ }
21337
+ namespace Create {
21338
+ type Body = CreateBody;
21339
+ type Result = Data;
21340
+ }
21341
+ namespace Update {
21342
+ type ID = StringId;
21343
+ type Body = UpdateBody;
21344
+ type Result = Data;
21345
+ }
21346
+ namespace Remove {
21347
+ type ID = StringId;
21348
+ interface Params {
21349
+ /**
21350
+ * A category still referenced by live sessions is rejected (400, with
21351
+ * `{ assigned_sessions, requires_force: true }`) unless `force: true`, which first
21352
+ * unsets `category` on those sessions and then soft-deletes the category.
21353
+ */
21354
+ force?: boolean;
21355
+ }
21356
+ type Result = Data;
21357
+ }
21358
+ }
21359
+ namespace AiObjectDetectionSegment {
21360
+ /**
21361
+ * A SEGMENT is a named label set used by share-of-shelf metrics ("our brand",
21362
+ * "competitor X", "energy drinks"). A metric references segments and may override a
21363
+ * segment's labels for that metric only — the segment stays the reusable default.
21364
+ */
21365
+ interface Data {
21366
+ _id: StringId;
21367
+ /** Unique per namespace among live segments. */
21368
+ name: string;
21369
+ description?: string;
21370
+ /** Member `ai-object-detection-label` ids. Empty = placeholder the metric must override. */
21371
+ labels: StringId[];
21372
+ disabled: boolean;
21373
+ /** Server-stamped from the caller's token on create. */
21374
+ creator?: AdminOrRepOrTenantOrClient;
21375
+ /** Server-stamped from the caller's token on update / remove. */
21376
+ editor?: AdminOrRepOrTenantOrClient;
21377
+ company_namespace: string[];
21378
+ createdAt: Date;
21379
+ updatedAt: Date;
21380
+ }
21381
+ type PopulatedKeys = "labels";
21382
+ interface DataWithPopulatedKeys extends Data {
21383
+ labels_populated?: AiObjectDetectionLabel.Data[];
21384
+ }
21385
+ interface CreateBody {
21386
+ /** Required and must be non-blank (400 otherwise). */
21387
+ name: string;
21388
+ description?: string;
21389
+ labels?: StringId[];
21390
+ company_namespace?: string[];
21391
+ }
21392
+ /** PUT re-validates the body: `name` must be present and non-blank even on a partial change. */
21393
+ interface UpdateBody {
21394
+ name: string;
21395
+ description?: string;
21396
+ labels?: StringId[];
21397
+ disabled?: boolean;
21398
+ }
21399
+ namespace Find {
21400
+ type Params = DefaultPaginationQueryParams & {
21401
+ _id?: StringId | StringId[];
21402
+ /** Exact match (single value or list). Use `search` for a substring match. */
21403
+ name?: string | string[];
21404
+ /** Case-insensitive regex on `name`. */
21405
+ search?: string;
21406
+ /** Segments containing any of these label id(s). */
21407
+ labels?: StringId | StringId[];
21408
+ disabled?: boolean;
21409
+ from_updatedAt?: number;
21410
+ to_updatedAt?: number;
21411
+ from_createdAt?: number;
21412
+ to_createdAt?: number;
21413
+ populatedKeys?: PopulatedKeys[];
21414
+ };
21415
+ interface Result extends DefaultPaginationResult {
21416
+ data: DataWithPopulatedKeys[];
21417
+ }
21418
+ }
21419
+ namespace Get {
21420
+ type ID = StringId;
21421
+ type Result = Data;
21422
+ }
21423
+ namespace Create {
21424
+ type Body = CreateBody;
21425
+ type Result = Data;
21426
+ }
21427
+ namespace Update {
21428
+ type ID = StringId;
21429
+ type Body = UpdateBody;
21430
+ type Result = Data;
21431
+ }
21432
+ namespace Remove {
21433
+ type ID = StringId;
21434
+ /** Soft-delete; rejected (400 naming the metrics) while an active share-of-shelf metric still references the segment. */
21435
+ type Result = Data;
21436
+ }
21437
+ }
21438
+ namespace AiObjectDetectionLabelReport {
21439
+ /** Dataset subset a task is routed to (`auto` fills train or val at build time; `ignore` is excluded). */
21440
+ type Subset = "train" | "val" | "test" | "auto" | "ignore";
21441
+ /** Annotation-group provenance: model output, human-drawn, or model output edited by a human. */
21442
+ type AnnotationState = "auto" | "manual" | "auto_edited";
21443
+ /** Per-annotation label provenance. */
21444
+ type LabelState = "auto" | "manual";
21445
+ /** Which group states count as training-eligible: `all` = manual|auto|auto_edited (model default), `smart` = manual|auto_edited. */
21446
+ type StateMode = "all" | "smart";
21447
+ /** YOLO-style pixel box on the frame image. */
21448
+ interface Box {
21449
+ x1: number;
21450
+ y1: number;
21451
+ x2: number;
21452
+ y2: number;
21453
+ }
21454
+ interface ItemGroup {
21455
+ usable: boolean;
21456
+ confirmed: boolean;
21457
+ annotation_state?: AnnotationState;
21458
+ /** Detector that produced the group: a trained model version or a zero-shot VLM. */
21459
+ engine?: "trained" | "zero_shot";
21460
+ /** `ai-object-detection-model-version` id when `engine` is `trained`. */
21461
+ model_version?: StringId;
21462
+ }
21463
+ /** One annotation of the requested label inside the requested dataset — the `items[]` row. */
21464
+ interface Data {
21465
+ /** `ai-object-detection-task` id the annotation belongs to. */
21466
+ task_id: StringId;
21467
+ /** Public URL of the task's frame image. */
21468
+ image_url?: string;
21469
+ /** Image width (task `shape[0]`, falling back to the media doc). */
21470
+ image_w?: number;
21471
+ /** Image height (task `shape[1]`, falling back to the media doc). */
21472
+ image_h?: number;
21473
+ /** Subset from THIS dataset's `task_dataset` entry. */
21474
+ subset?: Subset;
21475
+ box: Box;
21476
+ confidence?: number;
21477
+ label_state: LabelState;
21478
+ group: ItemGroup;
21479
+ /** `usable && confirmed && state ∈ mode set` AND subset ∈ {train, auto}. */
21480
+ included_in_training: boolean;
21481
+ /** `usable && confirmed && state ∈ mode set` AND subset ∈ {val, auto}. */
21482
+ included_in_val: boolean;
21483
+ /** Internal eligibility flag (`usable && confirmed && state ∈ mode set`) the pipeline leaves on the row. */
21484
+ _qualifies?: boolean;
21485
+ }
21486
+ /** Distribution of the label's annotations across the WHOLE dataset (not affected by item filters). */
21487
+ interface Summary {
21488
+ annotations_total: number;
21489
+ /** Distinct tasks carrying the label. */
21490
+ tasks_total: number;
21491
+ subset_train: number;
21492
+ subset_val: number;
21493
+ subset_test: number;
21494
+ subset_auto: number;
21495
+ subset_ignore: number;
21496
+ state_manual: number;
21497
+ state_auto: number;
21498
+ state_auto_edited: number;
21499
+ usable_count: number;
21500
+ confirmed_count: number;
21501
+ training_eligible: number;
21502
+ val_eligible: number;
21503
+ }
21504
+ /** Default mode response (`?dataset=&label=`): annotation health summary + paginated, filterable items. */
21505
+ interface ReportResult {
21506
+ summary: Summary;
21507
+ items: Data[];
21508
+ mode: StateMode;
21509
+ /** Count of items after the item filters (the paginated stream). */
21510
+ total_result: number;
21511
+ current_page: number;
21512
+ per_page: number;
21513
+ total_pages: number;
21514
+ }
21515
+ /** One measured annotation considered by the smart dims analyzer. */
21516
+ interface DimsSample {
21517
+ /** `ai-object-detection-task` id. */
21518
+ task: StringId;
21519
+ /** Parent `ai-object-detection-session` id (session frames only). */
21520
+ session?: StringId;
21521
+ frame_id?: number;
21522
+ createdAt: Date;
21523
+ /** Measured physical width (cm). */
21524
+ w_cm: number;
21525
+ /** Measured physical height (cm). */
21526
+ h_cm: number;
21527
+ confidence?: number;
21528
+ /** 0..1 depth confidence of the placement. */
21529
+ depth_confidence?: number;
21530
+ label_state: LabelState;
21531
+ /** Modified z-score of `w_cm` (2 dp); `null` when the MAD is 0 and the value differs from the median. */
21532
+ z_w: number | null;
21533
+ z_h: number | null;
21534
+ /** Kept for the proposal (not an outlier and depth confidence ≥ `min_depth_confidence`). */
21535
+ kept: boolean;
21536
+ reject_reason: "low_depth_confidence" | "outlier" | null;
21537
+ }
21538
+ interface DimsStats {
21539
+ /** Samples collected. */
21540
+ n: number;
21541
+ /** Samples with depth confidence ≥ `min_depth_confidence`. */
21542
+ n_qualified: number;
21543
+ /** Samples kept after outlier rejection. */
21544
+ n_kept: number;
21545
+ /** Median over qualified samples, 1 dp (cm). */
21546
+ median_w_cm: number;
21547
+ median_h_cm: number;
21548
+ /** Median absolute deviation over qualified samples, 2 dp (cm). */
21549
+ mad_w_cm: number;
21550
+ mad_h_cm: number;
21551
+ /** Mean of kept samples (1 dp); `null` when fewer than `min_samples` were kept. */
21552
+ proposed: {
21553
+ w_cm: number;
21554
+ h_cm: number;
21555
+ } | null;
21556
+ }
21557
+ /** `?mode=dims&label=` response — the label screen's SMART DIMS ANALYZER. */
21558
+ interface DimsResult {
21559
+ label: Pick<AiObjectDetectionLabel.Data, "_id" | "name" | "physical_size" | "label_group"> | null;
21560
+ /** Effective parameters after defaults/clamping. */
21561
+ params: {
21562
+ sample: number;
21563
+ z_threshold: number;
21564
+ min_depth_confidence: number;
21565
+ min_samples: number;
21566
+ };
21567
+ stats: DimsStats;
21568
+ samples: DimsSample[];
21569
+ }
21570
+ namespace Find {
21571
+ /** Default mode: per-dataset annotation health for one label. */
21572
+ interface ReportParams {
21573
+ /** Eligibility predicate; defaults to `all`. Any value other than `smart`/`dims` is treated as `all`. */
21574
+ mode?: StateMode;
21575
+ /** `ai-object-detection-dataset` id (required, ObjectId). */
21576
+ dataset: StringId;
21577
+ /** `ai-object-detection-label` id (required, ObjectId). */
21578
+ label: StringId;
21579
+ /** Item filter — one value or a list. */
21580
+ subset?: Subset | Subset[];
21581
+ /** Item filter on `group.annotation_state`. */
21582
+ annotation_state?: AnnotationState | AnnotationState[];
21583
+ /** Item filter on the annotation's `label_state`. */
21584
+ label_state?: LabelState | LabelState[];
21585
+ /** Item filter on `group.usable`. */
21586
+ usable?: boolean;
21587
+ /** Item filter on `group.confirmed`. */
21588
+ confirmed?: boolean;
21589
+ /** Item filter: only rows included in training (`train`) or validation (`val`). */
21590
+ included?: "train" | "val";
21591
+ /** Page size for `items` (capped by the server's pagination max). */
21592
+ per_page?: number;
21593
+ /** 1-based page for `items`. */
21594
+ page?: number;
21595
+ }
21596
+ /** Smart dims analyzer — label-scoped, no dataset. */
21597
+ interface DimsParams {
21598
+ mode: "dims";
21599
+ /** `ai-object-detection-label` id (required, ObjectId). */
21600
+ label: StringId;
21601
+ /** Most recent measured annotations to consider. Default 100, clamped to 1..500. */
21602
+ sample?: number;
21603
+ /** Modified z-score cut-off on either axis. Default 3.5. */
21604
+ z_threshold?: number;
21605
+ /** Minimum depth confidence for a sample to count. Default 0.5. */
21606
+ min_depth_confidence?: number;
21607
+ /** Minimum kept samples before a proposal is made. Default 8. */
21608
+ min_samples?: number;
21609
+ }
21610
+ type Params = ReportParams | DimsParams;
21611
+ /** `ReportResult` for the default mode, `DimsResult` when `mode === "dims"`. */
21612
+ type Result = ReportResult | DimsResult;
21613
+ }
21614
+ }
21615
+ namespace AiObjectDetectionSettings {
21616
+ /**
21617
+ * Free-form bag of ANALYZE knobs (numbers / booleans only). Known keys are the
21618
+ * scene engine's `SceneMathConfig` (see `AiObjectDetectionInference.SceneMathConfig`)
21619
+ * plus a few the analyze endpoint reads directly (e.g. `inference_concurrency`).
21620
+ * The server does NOT filter keys — only values are validated: each knob must be a
21621
+ * finite number or a boolean; `null`/`undefined` knobs are skipped ("fall back to
21622
+ * the engine default"); anything else is rejected with 400. Capped at 200 keys.
21623
+ */
21624
+ type AnalyzeConfig = AiObjectDetectionInference.SceneMathConfig & {
21625
+ /** Parallel per-frame inference calls during a session analysis run. */
21626
+ inference_concurrency?: number;
21627
+ [knob: string]: number | boolean | undefined;
21628
+ };
21629
+ /**
21630
+ * The stored Mongo document (exactly one per namespace, unique index on
21631
+ * `company_namespace`). Never returned as-is — every method answers the projected
21632
+ * `Data` shape below.
21633
+ */
21634
+ interface Document {
21635
+ _id: StringId;
21636
+ company_namespace: string[];
21637
+ disabled: boolean;
21638
+ /** Partial knob bag (see `AnalyzeConfig`). */
21639
+ analyze_config?: AnalyzeConfig;
21640
+ createdAt: Date;
21641
+ updatedAt: Date;
21642
+ }
21643
+ /** Response shape of EVERY method (find / get / create / update / patch / remove). */
21644
+ interface Data {
21645
+ /** `null` while the namespace has never saved defaults. */
21646
+ _id: StringId | null;
21647
+ /** `true` = nothing saved for this namespace; clients fall back to the engine defaults. */
21648
+ is_default: boolean;
21649
+ /** The saved partial knob bag (`{}` when `is_default`). */
21650
+ analyze_config: AnalyzeConfig;
21651
+ /** When the defaults were last saved; `null` when `is_default`. */
21652
+ updatedAt: Date | null;
21653
+ }
21654
+ interface CreateBody {
21655
+ /** Required — the server rejects a missing / non-object bag with 400. */
21656
+ analyze_config: AnalyzeConfig;
21657
+ /**
21658
+ * Optional tenant namespace override for SDK callers. NOTE: this endpoint keys
21659
+ * its single document from the caller's token namespace and does not read the
21660
+ * body's `company_namespace`.
21661
+ */
21662
+ company_namespace?: string[];
21663
+ }
21664
+ /** PUT / PATCH accept exactly the create body — they upsert the same single document. */
21665
+ type UpdateBody = {
21666
+ analyze_config: AnalyzeConfig;
21667
+ };
21668
+ namespace Find {
21669
+ /** NOT paginated — the namespace's single defaults document. Never 404s. */
21670
+ type Result = Data;
21671
+ }
21672
+ namespace Get {
21673
+ /** Ignored — one logical document per namespace (the dashboard sends `defaults`). */
21674
+ type ID = StringId;
21675
+ type Result = Data;
21676
+ }
21677
+ namespace Create {
21678
+ type Body = CreateBody;
21679
+ /** The fresh read after the upsert. */
21680
+ type Result = Data;
21681
+ }
21682
+ namespace Update {
21683
+ /** Ignored — the namespace keys the document. */
21684
+ type ID = StringId;
21685
+ type Body = UpdateBody;
21686
+ type Result = Data;
21687
+ }
21688
+ namespace Patch {
21689
+ /** Identical to update/create (upsert of the single document; no id, no query). */
21690
+ type Body = UpdateBody;
21691
+ type Result = Data;
21692
+ }
21693
+ namespace Remove {
21694
+ /** Ignored — any value works (the dashboard sends `defaults`). */
21695
+ type ID = StringId;
21696
+ /** The empty default state: `{ _id: null, is_default: true, analyze_config: {}, updatedAt: null }`. */
21697
+ type Result = Data;
21698
+ }
21699
+ }
21700
+ namespace AiObjectDetectionDetectionSettings {
21701
+ /**
21702
+ * Two-tier frame limit. `warn` = guidance shown on the device, frame still usable;
21703
+ * `error` = frame excluded from election (but it still counts toward the coverage
21704
+ * total). `warn` is always the softer bound.
21705
+ */
21706
+ interface WarnError {
21707
+ warn: number;
21708
+ error: number;
21709
+ }
21710
+ type CaptureMode = "stack_elect" | "continuous" | "burst";
21711
+ type CaptureResolution = "medium" | "high" | "max";
21712
+ interface CaptureConfig {
21713
+ /** Default `stack_elect`. */
21714
+ mode: CaptureMode;
21715
+ /** Capture rate, frames per second. Default 4. */
21716
+ rate_hz: number;
21717
+ /** Default `max`. */
21718
+ resolution: CaptureResolution;
21719
+ /** Seconds before a stack-elect capture times out. Default 30. */
21720
+ stack_timeout_s: number;
21721
+ /** Show the sweep guide overlay. Default true. */
21722
+ sweep_guide: boolean;
21723
+ /** Default true. */
21724
+ torch: boolean;
21725
+ /** Default true. */
21726
+ exposure_lock: boolean;
21727
+ }
21728
+ interface FrameConfig {
21729
+ /** Variance-of-Laplacian sharpness floor: `warn` = live-guidance floor, `error` = election hard floor ("blur limit"). Default `{ warn: 200, error: 50 }`. */
21730
+ min_sharpness: WarnError;
21731
+ /** Degrees. Default `{ warn: 18, error: 30 }`. */
21732
+ max_yaw_delta_deg: WarnError;
21733
+ /** Degrees. Default `{ warn: 15, error: 28 }`. */
21734
+ max_pitch_delta_deg: WarnError;
21735
+ /** Degrees. Default `{ warn: 12, error: 25 }`. */
21736
+ max_roll_delta_deg: WarnError;
21737
+ /** Metres. Default `{ warn: 0.8, error: 1.5 }`. */
21738
+ max_depth_variation_m: WarnError;
21739
+ /** Metres — `error` is the NEARER bound (worse). Default `{ warn: 0.5, error: 0.3 }`. */
21740
+ min_distance_m: WarnError;
21741
+ /** Metres — `error` is the FARTHER bound (worse). Default `{ warn: 2.0, error: 3.0 }`. */
21742
+ max_distance_m: WarnError;
21743
+ /** 0..100. Default `{ warn: 60, error: 35 }`. */
21744
+ min_tracking_score: WarnError;
21745
+ }
21746
+ interface ElectionConfig {
21747
+ /** Stop electing at this union coverage (0..1). Default 0.985. */
21748
+ cover_target: number;
21749
+ /** Ignore gains under this fraction of a median footprint. Default 0.02. */
21750
+ sliver_frac: number;
21751
+ /** Redundancy penalty exponent (score = quality · gain^exp). Default 1.5. */
21752
+ gain_exp: number;
21753
+ /** Quality geometric-mean weight — sharpness. Default 0.4. */
21754
+ w_sharp: number;
21755
+ /** Quality geometric-mean weight — depth. Default 0.3. */
21756
+ w_depth: number;
21757
+ /** Quality geometric-mean weight — tracking. Default 0.2. */
21758
+ w_track: number;
21759
+ /** Quality geometric-mean weight — lux. Default 0.1. */
21760
+ w_lux: number;
21761
+ }
21762
+ /** Average-quality verdict bands; below `acceptable` = rejected. Default `{ excellent: 0.8, good: 0.65, acceptable: 0.45 }`. */
21763
+ interface SessionScoreBands {
21764
+ excellent: number;
21765
+ good: number;
21766
+ acceptable: number;
21767
+ }
21768
+ interface SessionConfig {
21769
+ /** Reject when the device-reported covered area (m²) is below this. Default 0.5. */
21770
+ coverage_target_m2: number;
21771
+ /** When false, a spatial jump (after removing error frames) rejects the session. Default false. */
21772
+ allow_jump: boolean;
21773
+ /** Elected-count budget: ceil(total_area / avg_frame_area × allowance); more elected frames than that rejects the session. Default 1.6. */
21774
+ elected_allowance: number;
21775
+ score: SessionScoreBands;
21776
+ }
21777
+ /** The full, MERGED detection config the mobile app pulls on session start (defaults deep-merged with the namespace overrides). */
21778
+ interface DetectionConfig {
21779
+ capture: CaptureConfig;
21780
+ frame: FrameConfig;
21781
+ election: ElectionConfig;
21782
+ session: SessionConfig;
21783
+ }
21784
+ /**
21785
+ * Deep-partial overrides. Objects merge recursively over the engine defaults;
21786
+ * scalars replace. Unknown keys are kept by the merge so the config can grow.
21787
+ */
21788
+ interface DetectionConfigOverrides {
21789
+ capture?: Partial<CaptureConfig>;
21790
+ frame?: {
21791
+ [K in keyof FrameConfig]?: Partial<WarnError>;
21792
+ };
21793
+ election?: Partial<ElectionConfig>;
21794
+ session?: Partial<Omit<SessionConfig, "score">> & {
21795
+ score?: Partial<SessionScoreBands>;
21796
+ };
21797
+ }
21798
+ /**
21799
+ * The stored Mongo document (exactly one per namespace, unique index on
21800
+ * `company_namespace`). Never returned as-is — reads answer the projected `Data`.
21801
+ */
21802
+ interface Document {
21803
+ _id: StringId;
21804
+ company_namespace: string[];
21805
+ disabled: boolean;
21806
+ /** Partial overrides over the engine defaults. */
21807
+ config?: DetectionConfigOverrides;
21808
+ createdAt: Date;
21809
+ updatedAt: Date;
21810
+ }
21811
+ /** Response shape of find / get / create / update / patch. */
21812
+ interface Data {
21813
+ /** `null` while the namespace has never saved overrides. */
21814
+ _id: StringId | null;
21815
+ /** `true` = no overrides saved; `config` is the pure engine default. */
21816
+ is_default: boolean;
21817
+ /** Merged result: namespace overrides deep-merged over the engine defaults. */
21818
+ config: DetectionConfig;
21819
+ /** The raw saved overrides (`{}` when `is_default`). */
21820
+ overrides: DetectionConfigOverrides;
21821
+ /** When the overrides were last saved; `null` when `is_default`. */
21822
+ updatedAt: Date | null;
21823
+ }
21824
+ interface CreateBody {
21825
+ /**
21826
+ * Required (400 when missing / not an object). REPLACES the stored overrides
21827
+ * object wholesale — it is not merged into previously saved overrides — so send
21828
+ * the complete set of overrides you want kept.
21829
+ */
21830
+ config: DetectionConfigOverrides;
21831
+ /**
21832
+ * Optional tenant namespace override for SDK callers. NOTE: this endpoint keys
21833
+ * its single document from the caller's token namespace and does not read the
21834
+ * body's `company_namespace`.
21835
+ */
21836
+ company_namespace?: string[];
21837
+ }
21838
+ /** PUT / PATCH accept exactly the create body — they upsert the same single document. */
21839
+ type UpdateBody = {
21840
+ config: DetectionConfigOverrides;
21841
+ };
21842
+ namespace Find {
21843
+ /** NOT paginated — the namespace's merged config. Never 404s. */
21844
+ type Result = Data;
21845
+ }
21846
+ namespace Get {
21847
+ /** Ignored — one logical document per namespace. */
21848
+ type ID = StringId;
21849
+ type Result = Data;
21850
+ }
21851
+ namespace Create {
21852
+ type Body = CreateBody;
21853
+ /** The fresh merged read after the upsert. */
21854
+ type Result = Data;
21855
+ }
21856
+ namespace Update {
21857
+ /** Ignored — the namespace keys the document. */
21858
+ type ID = StringId;
21859
+ type Body = UpdateBody;
21860
+ type Result = Data;
21861
+ }
21862
+ namespace Patch {
21863
+ /** Identical to update/create (upsert of the single document; no id, no query). */
21864
+ type Body = UpdateBody;
21865
+ type Result = Data;
21866
+ }
21867
+ namespace Remove {
21868
+ /** Ignored — any value works. */
21869
+ type ID = StringId;
21870
+ /** Reset acknowledgement: `is_default: true` plus the pure engine defaults. Note: NO `_id` / `overrides` / `updatedAt` in this response. */
21871
+ interface Result {
21872
+ is_default: true;
21873
+ config: DetectionConfig;
21874
+ }
21875
+ }
21876
+ }
21877
+ namespace AiObjectDetectionInference {
21878
+ /** Detector selection on the request. */
21879
+ type Engine = "auto" | "trained" | "zero_shot";
21880
+ /** The detector that actually ran. */
21881
+ type ResolvedEngine = "trained" | "zero_shot";
21882
+ /** Zero-shot VLMs the server accepts (`zero_shot_model`); unknown names are rejected with 400. */
21883
+ type ZeroShotModel = "qwen/qwen3-vl-8b-instruct" | "qwen/qwen3-vl-32b-instruct" | "qwen/qwen3-vl-235b-a22b-instruct" | "qwen/qwen2.5-vl-72b-instruct";
21884
+ type ExplainPart = "class_scores" | "heatmap" | "gradcam" | "feature_maps" | "embeddings" | "confusion_matrix" | "all";
21885
+ type EmbedMethod = "auto" | "umap" | "tsne" | "pca";
21886
+ /** Why a detection could NOT be placed in world coordinates. */
21887
+ type IgnoreReason = "no_pose" | "no_intrinsics" | "no_depth" | "empty_depth_region" | "insufficient_depth_pixels" | "behind_shelf";
21888
+ /**
21889
+ * Every knob of the AR scene engine (`scene-math.ts` SceneMathConfig). All optional;
21890
+ * the engine default is given per field. Inference honours the depth-sampling knobs
21891
+ * (`min_depth_m` … `shelf_tolerance_m`), the dims-reclassifier knobs and the size-gate
21892
+ * knobs; the clustering / plane-merge / shelf / point-cloud knobs are consumed by
21893
+ * `ai-object-detection-session-analysis` and are accepted-but-ignored here. The same
21894
+ * bag is what `ai-object-detection-settings` stores as the namespace analyze defaults.
21895
+ */
21896
+ interface SceneMathConfig {
21897
+ /** Base world distance (m) to merge two detections into one object. Default 0.08. */
21898
+ cluster_eps_m?: number;
21899
+ /** Extra merge radius (m) when labels match. Default 0.02. */
21900
+ class_agree_bonus_m?: number;
21901
+ /** A scene object absorbs at most ONE detection per frame. Default true. */
21902
+ block_same_frame?: boolean;
21903
+ /** Ignore depth samples below this (m). Default 0.05. */
21904
+ min_depth_m?: number;
21905
+ /** Ignore depth samples above this (m). Default 6. */
21906
+ max_depth_m?: number;
21907
+ /** Minimum ARKit depth-confidence (0/1/2) to keep a pixel. Default 1. */
21908
+ conf_threshold?: number;
21909
+ /** Percentile of bbox depths to take — front-biased. Default 30. */
21910
+ front_percentile?: number;
21911
+ /** Depth-gate slack (m) beyond the frame's `distance_to_shelf_m`. Default 0.3. */
21912
+ shelf_tolerance_m?: number;
21913
+ /** Same-label 2D IoU at/above this = duplicate box within one frame. Default 0.92. */
21914
+ same_frame_iou_thresh?: number;
21915
+ /** Same-label world distance (m) below this = same physical spot within one frame. Default 0.02. */
21916
+ same_frame_min_separation_m?: number;
21917
+ /** Drop detections under this detector confidence (0 = off). Default 0. */
21918
+ min_detection_confidence?: number;
21919
+ /** Drop when the front face is smaller than this (cm). Default 0.5. */
21920
+ min_object_size_cm?: number;
21921
+ /** Drop when the front face is larger than this (cm). Default 500. */
21922
+ max_object_size_cm?: number;
21923
+ /** Use plane-projection matching across frames. Default true. */
21924
+ plane_merge?: boolean;
21925
+ /** Min projected-rect IoU to merge. Default 0.1. */
21926
+ plane_merge_iou?: number;
21927
+ /** Max |plane-depth difference| (m) — the "virtual object depth". Default 0.25. */
21928
+ plane_merge_depth_delta_m?: number;
21929
+ /** Dims reclassifier master switch — re-label an auto detection to a sibling label whose expected dims fit its measured size better. Default false. */
21930
+ reclassify_labels?: boolean;
21931
+ /** E(original) ≤ this keeps the detected label outright. Default 0.15. */
21932
+ reclassify_keep_dev?: number;
21933
+ /** A sibling must fit within this to steal the detection. Default 0.15. */
21934
+ reclassify_target_dev?: number;
21935
+ /** Base margin E(orig) − E(best) must exceed. Default 0.06. */
21936
+ reclassify_min_margin?: number;
21937
+ /** Margin × (1 + scale · detector_conf); 0 = ignore confidence. Default 0.5. */
21938
+ reclassify_conf_margin_scale?: number;
21939
+ /** Weight of the SIZE mismatch in the fit score. Default 1.0. */
21940
+ reclassify_weight_scale?: number;
21941
+ /** Weight of the SHAPE (aspect) mismatch in the fit score. Default 0.5. */
21942
+ reclassify_weight_aspect?: number;
21943
+ /** Skip detections whose depth confidence is below this. Default 0.5. */
21944
+ reclassify_min_depth_confidence?: number;
21945
+ /** Post-walk: merge overlapping same-group clusters via the plane test. Default true. */
21946
+ group_consensus_merge?: boolean;
21947
+ /** Size gate master switch — flag detections measuring beyond their label's expected dims. Default true. */
21948
+ size_gate?: boolean;
21949
+ /** Per-axis allowance: reject when measured w or h > (1 + this) × expected. Default 0.35. */
21950
+ size_gate_dims_allowance?: number;
21951
+ /** Area allowance: reject when measured w·h > (1 + this) × expected area. Default 0.35. */
21952
+ size_gate_area_allowance?: number;
21953
+ /** Skip gating detections whose depth confidence is below this. Default 0.5. */
21954
+ size_gate_min_depth_confidence?: number;
21955
+ /** Shelf composition (boards → stacks → objects) master switch. Default true. */
21956
+ shelf_analysis?: boolean;
21957
+ /** Min horizontal overlap ratio (of the narrower) to belong to the same column. Default 0.5. */
21958
+ shelf_support_min_overlap?: number;
21959
+ /** Bottom-density window half-width (m) when finding candidate levels. Default 0.06. */
21960
+ shelf_gap_split_m?: number;
21961
+ /** Min vertical distance (m) between two shelf boards. Default 0.12. */
21962
+ shelf_min_spacing_m?: number;
21963
+ /** Measurement slack (m) for stacked boxes. Default 0.035. */
21964
+ shelf_stack_max_penetration_m?: number;
21965
+ /** Depth gap (m) separating front/back rows within a shelf. Default 0.15. */
21966
+ shelf_row_split_m?: number;
21967
+ /** A level needs at least this many stacks to be a shelf. Default 1. */
21968
+ shelf_min_stacks?: number;
21969
+ /** Build + store the voxel point cloud on analysis runs. Default true. */
21970
+ build_point_cloud?: boolean;
21971
+ /** Depth-grid stride — every Nth pixel. Default 2. */
21972
+ pc_stride?: number;
21973
+ /** Voxel edge (m) for downsampling. Default 0.02. */
21974
+ pc_voxel_size_m?: number;
21975
+ /** RANSAC: planes to peel off at most. Default 4. */
21976
+ ransac_max_planes?: number;
21977
+ /** RANSAC: stop below this inlier share. Default 0.05. */
21978
+ ransac_min_inlier_ratio?: number;
21979
+ /** RANSAC: point-to-plane inlier distance (m). Default 0.02. */
21980
+ ransac_distance_thresh_m?: number;
21981
+ }
21982
+ /** The action request — `POST /ai-object-detection-inference`. */
21983
+ interface CreateBody {
21984
+ /** The `ai-object-detection-task` to infer on (must belong to the caller's namespace and carry `file_media` with a public URL). */
21985
+ task_id: StringId;
21986
+ /** Model version `_id`. When set, resolves the lambda model + label order directly and bypasses the task's dataset / default-model requirement. */
21987
+ model_version?: StringId;
21988
+ /** Object-detection model `_id`. Used directly as the lambda model id when given (its `current_model_version` supplies the label order). */
21989
+ model?: StringId;
21990
+ /** Default `auto`: trained when a model resolves, else falls back to zero-shot (when configured) ONLY on the dataset path; explicit `model`/`model_version` stay strict. */
21991
+ engine?: Engine;
21992
+ /** Zero-shot VLM override. */
21993
+ zero_shot_model?: ZeroShotModel;
21994
+ /** Detection confidence threshold. Defaults to the model's `predict_settings[0].conf`, else 0.25. */
21995
+ conf?: number;
21996
+ /** NMS IoU threshold (0..1). Defaults to the model's `predict_settings[0].iou`, else 0.7. */
21997
+ iou?: number;
21998
+ /** Class-agnostic NMS (trained engine only). Defaults to the model's `predict_settings[0].agnostic_nms`, else false. */
21999
+ agnostic_nms?: boolean;
22000
+ /** Default true — persist the mapped predictions as a fresh `auto` annotation group on the task (replacing only the unconfirmed auto group from the same source). */
22001
+ save?: boolean;
22002
+ /** Explainable-AI opt-in (trained engine only): calls the diagnosis lambda and returns `xai`; when saved, XAI is persisted on the group. Never fails the prediction. */
22003
+ explain?: boolean;
22004
+ /** Subset of XAI parts. Omit / empty / `["all"]` = every part; unknown names are dropped. */
22005
+ explain_parts?: ExplainPart[];
22006
+ /** Candidate classes per detection in `class_scores`. Default 5 (min 1). */
22007
+ explain_topk?: number;
22008
+ /** Top-K cap on per-detection Grad-CAM maps. Default 20; 0 disables them. */
22009
+ explain_gradcam_detections?: number;
22010
+ /** 2D projection for the embeddings scatter. Default `auto`. */
22011
+ explain_embed_method?: EmbedMethod;
22012
+ /** XAI backfill for the EXISTING detections (implies `explain`; trained engine + `save` only): the detector is skipped and XAI is grafted onto the task's saved unconfirmed auto group of the resolved version. Falls through to a full inference when no such group exists. 400 when the resolved engine is zero-shot. */
22013
+ explain_only?: boolean;
22014
+ /** Back-projection / reclassifier / size-gate tuning for AR session frames. Omit for the engine defaults. */
22015
+ config?: SceneMathConfig;
22016
+ /** Optional tenant namespace override for SDK callers. NOTE: the server takes the namespace from the caller's token and does not read this field. */
22017
+ company_namespace?: string[];
22018
+ }
22019
+ /** Raw detector box: pixel corners on the original image plus YOLO-normalized center/size (0..1). */
22020
+ interface PredictionBox {
22021
+ x1: number;
22022
+ y1: number;
22023
+ x2: number;
22024
+ y2: number;
22025
+ x_center: number;
22026
+ y_center: number;
22027
+ width: number;
22028
+ height: number;
22029
+ }
22030
+ /** One raw detector result (`images[0].results[]` of the Ultralytics lambda; zero-shot replies are normalized to the same shape with `class: -1`). Passed through unchanged. */
22031
+ interface Prediction {
22032
+ name: string;
22033
+ class: number;
22034
+ confidence: number;
22035
+ box: PredictionBox;
22036
+ [key: string]: any;
22037
+ }
22038
+ /** A mapped detection — the same schema as a task annotation (`AiObjectDetectionTask.Annotation`). World fields appear only for placed AR frames; `ignore_reason` names why placement failed. */
22039
+ type Annotation = AiObjectDetectionTask.Annotation;
22040
+ interface XaiBox {
22041
+ x1: number;
22042
+ y1: number;
22043
+ x2: number;
22044
+ y2: number;
22045
+ }
22046
+ interface XaiCandidate {
22047
+ class: number;
22048
+ name: string;
22049
+ score: number;
22050
+ }
22051
+ /** Per-detection top-k class scores of the pre-NMS anchor that produced the detection. `index` = position in `predictions`. */
22052
+ interface XaiClassScore {
22053
+ index: number;
22054
+ class: number;
22055
+ name: string;
22056
+ confidence: number;
22057
+ /** Detection box in original-image pixels (xyxy). */
22058
+ box: XaiBox;
22059
+ /** Top-k candidate classes of the winning anchor, best first. */
22060
+ candidates: XaiCandidate[];
22061
+ /** IoU between the detection box and the matched pre-NMS anchor box. */
22062
+ match_iou?: number;
22063
+ }
22064
+ /** EigenCAM activation heatmap ("where did the model look"). RGBA PNG, alpha = activation. */
22065
+ interface XaiHeatmap {
22066
+ png_base64: string;
22067
+ method: "eigencam";
22068
+ layers?: string;
22069
+ width: number;
22070
+ height: number;
22071
+ }
22072
+ /** One detection's own Grad-CAM map ("why THIS box, this class"). `index` = position in `predictions`. */
22073
+ interface XaiGradcamDetection {
22074
+ index: number;
22075
+ class: number;
22076
+ name: string;
22077
+ confidence: number;
22078
+ /** Detection box in original-image pixels (xyxy). */
22079
+ box: XaiBox;
22080
+ match_iou?: number;
22081
+ png_base64: string;
22082
+ method?: "gradcam";
22083
+ layers?: string;
22084
+ target?: string;
22085
+ width: number;
22086
+ height: number;
22087
+ }
22088
+ /** TRUE gradient Grad-CAM ("what evidence drove the detections"). RGBA PNG, alpha = activation. */
22089
+ interface XaiGradcam {
22090
+ png_base64: string;
22091
+ method: "gradcam";
22092
+ layers?: string;
22093
+ /** What was backpropagated (the Grad-CAM objective). */
22094
+ target?: string;
22095
+ /** Input size of the gradient pass (capped, default 960). */
22096
+ imgsz?: number;
22097
+ width: number;
22098
+ height: number;
22099
+ per_detection?: XaiGradcamDetection[];
22100
+ /** Present when detections were truncated to the top-K. */
22101
+ per_detection_note?: string;
22102
+ }
22103
+ /** One per-stage feature-map grid (ultralytics visualize=True), downscaled JPEG. */
22104
+ interface XaiFeatureMap {
22105
+ /** Network stage, e.g. `stage12_C2f`. */
22106
+ stage: string;
22107
+ jpg_base64: string;
22108
+ }
22109
+ interface XaiEmbeddingPoint {
22110
+ /** Position of the detection in `predictions`. */
22111
+ index: number;
22112
+ class: number;
22113
+ name: string;
22114
+ confidence: number;
22115
+ /** Projected coordinate, min-max normalized to [0, 1]. */
22116
+ x: number;
22117
+ /** Projected coordinate, min-max normalized to [0, 1]. */
22118
+ y: number;
22119
+ }
22120
+ interface XaiEmbeddings {
22121
+ /** The projection that actually ran (`none` for a single point). */
22122
+ method: "umap" | "tsne" | "pca" | "none";
22123
+ /** What the caller asked for (differs from `method` on fallback). */
22124
+ requested_method?: EmbedMethod;
22125
+ layer?: string;
22126
+ /** Dimensionality of the pooled embedding before projection. */
22127
+ embedding_dim?: number;
22128
+ points: XaiEmbeddingPoint[];
22129
+ }
22130
+ /** Training-time confusion-matrix image URLs of the model version. */
22131
+ interface XaiConfusionMatrix {
22132
+ url: string | null;
22133
+ normalized_url: string | null;
22134
+ source: "training_artifacts";
22135
+ }
22136
+ /**
22137
+ * Raw diagnosis-lambda Explainable-AI payload (passthrough). Any part can be
22138
+ * missing — its failure reason is then appended to `notes`. When the diagnosis
22139
+ * detections do not align with `predictions`, the index-bearing parts
22140
+ * (`class_scores`, `gradcam.per_detection`, `embeddings.points`) are dropped and
22141
+ * a note explains why.
22142
+ */
22143
+ interface Xai {
22144
+ /** The parts that were requested. */
22145
+ parts?: string[];
22146
+ /** YOLO class index (stringified) → class name, from the model weights. */
22147
+ class_names?: {
22148
+ [classIndex: string]: string;
22149
+ };
22150
+ notes?: string[];
22151
+ class_scores?: XaiClassScore[];
22152
+ heatmap?: XaiHeatmap;
22153
+ gradcam?: XaiGradcam;
22154
+ feature_maps?: XaiFeatureMap[];
22155
+ embeddings?: XaiEmbeddings;
22156
+ confusion_matrix?: XaiConfusionMatrix | null;
22157
+ }
22158
+ /** The action response (`Create.Result`). There is no stored document for this service. */
22159
+ interface Data {
22160
+ task_id: StringId;
22161
+ /** The detector that actually ran. */
22162
+ engine: ResolvedEngine;
22163
+ /** The VLM used when `engine` is `zero_shot`, else `null`. */
22164
+ zero_shot_model: ZeroShotModel | null;
22165
+ /** Resolved model `_id`; `null` for zero-shot runs. */
22166
+ model: StringId | null;
22167
+ /** Resolved model version `_id`, or `null`. */
22168
+ model_version: StringId | null;
22169
+ /** Public URL of the task image that was inferred. */
22170
+ image_url: string;
22171
+ /** `[height, width]` of the inferred image (detector-reported). `null` on an `explained_existing` response; may be absent if the detector omits it. */
22172
+ shape?: number[] | null;
22173
+ /** The confidence threshold that was applied. */
22174
+ conf: number;
22175
+ /** Whether the annotation group (or the grafted XAI) was persisted on the task. */
22176
+ saved: boolean;
22177
+ /** Present (true) when `explain_only` grafted XAI onto the existing group — no detector ran: `predictions` is empty, `shape`/`placed_count`/`unplaced_count`/`unmapped` are `null`, and `annotations` echoes the existing group's annotations. */
22178
+ explained_existing?: true;
22179
+ annotations_count: number;
22180
+ /** Annotations placed in world coordinates (0 for non-session tasks). `null` on `explained_existing`. */
22181
+ placed_count: number | null;
22182
+ /** Annotations left 2D-only — each carries an `ignore_reason`. `null` on `explained_existing`. */
22183
+ unplaced_count: number | null;
22184
+ /** Predictions that could not be mapped to a label. `null` on `explained_existing`. */
22185
+ unmapped: number | null;
22186
+ annotations: Annotation[];
22187
+ /** Raw detector predictions (passthrough). */
22188
+ predictions: Prediction[];
22189
+ /** Present only when `explain`/`explain_only` ran on the trained engine and the diagnosis lambda succeeded, else `null`. */
22190
+ xai: Xai | null;
22191
+ /** The saved task document when the group was persisted, else `null`. */
22192
+ task: AiObjectDetectionTask.Data | null;
22193
+ }
22194
+ namespace Create {
22195
+ type Body = CreateBody;
22196
+ type Result = Data;
22197
+ }
22198
+ }
22199
+ namespace AiObjectDetectionTask {
22200
+ type Subset = "train" | "val" | "test" | "auto" | "ignore";
22201
+ /** Provenance of a whole annotation group. */
22202
+ type AnnotationState = "auto" | "manual" | "auto_edited";
22203
+ /** Provenance of a single box. */
22204
+ type LabelState = "auto" | "manual";
22205
+ /** Which detector produced a group: a trained model version (default) or a zero-shot VLM. */
22206
+ type Engine = "trained" | "zero_shot";
22207
+ /** Why a detection could NOT be placed in world coordinates (unset when placed). */
22208
+ type IgnoreReason = "no_pose" | "no_intrinsics" | "no_depth" | "empty_depth_region" | "insufficient_depth_pixels" | "behind_shelf";
22209
+ type ReclassificationReason = "dims_match_sibling" | "group_consensus" | "manual";
22210
+ /**
22211
+ * Mutually-exclusive review status used by the `annotation_status` find filter:
22212
+ * `pending` = not annotated (or no groups); `confirmed` = annotated and the leading
22213
+ * group is confirmed; `manual` / `auto_edited` = annotated, unconfirmed leading group
22214
+ * with that state; `auto` = every remaining annotated, unconfirmed leading group.
22215
+ */
22216
+ type AnnotationStatus = "pending" | "confirmed" | "manual" | "auto_edited" | "auto";
22217
+ /** Normalized box. Inference / the dashboard store YOLO center-size here: `x1` = cx, `y1` = cy, `x2` = w, `y2` = h. */
22218
+ interface Box {
22219
+ x1: number;
22220
+ x2: number;
22221
+ y1: number;
22222
+ y2: number;
22223
+ }
22224
+ /** Back-projected centroid in world coordinates, metres. */
22225
+ interface WorldPosition {
22226
+ x: number;
22227
+ y: number;
22228
+ z: number;
22229
+ }
22230
+ /** Physical front-face size, centimetres (`w` = world-horizontal, `h` = vertical). */
22231
+ interface WorldSize {
22232
+ w: number;
22233
+ h: number;
22234
+ }
22235
+ /** Size-gate provenance — set when the measured size exceeded the label's expected dims beyond the allowance. */
22236
+ interface SizeRejectDetail {
22237
+ exceeded?: "width" | "height" | "area";
22238
+ measured_w_cm?: number;
22239
+ measured_h_cm?: number;
22240
+ expected_w_cm?: number;
22241
+ expected_h_cm?: number;
22242
+ /** measured / (expected × (1 + allowance)) for the tripped check. */
22243
+ ratio?: number;
22244
+ }
22245
+ interface Annotation {
22246
+ _id?: StringId;
22247
+ box: Box;
22248
+ /** Default 0.25. */
22249
+ confidence?: number;
22250
+ /** `ai-object-detection-label` `_id`. */
22251
+ label_id: StringId;
22252
+ label_state: LabelState;
22253
+ /** Snapshot of the box geometry world placement last ran for; a box that no longer matches it is re-placed on the next save. */
22254
+ placed_box?: Box;
22255
+ world_position?: WorldPosition;
22256
+ world_size?: WorldSize;
22257
+ /** Metres — front-biased percentile (default p30) of the depth pixels over the whole bbox. */
22258
+ depth_at_center?: number;
22259
+ /** Normalized 0..1 depth confidence. */
22260
+ depth_confidence?: number;
22261
+ /** detection_conf × depth_conf × tracking, 0..1. */
22262
+ placement_confidence?: number;
22263
+ /** Link to the analysis object (`objects[]._id` on `ai-object-detection-session-analysis`) this detection was clustered into. */
22264
+ cluster_id?: StringId;
22265
+ ignore_reason?: IgnoreReason;
22266
+ /** When the dims reclassifier moved this annotation to a sibling label, the FIRST original label is kept here. */
22267
+ original_label?: StringId;
22268
+ reclassification_reason?: ReclassificationReason;
22269
+ /** Flagged (never deleted) when the measured size exceeded the label's expected dims; cleared when a later run passes it. */
22270
+ size_rejected?: boolean;
22271
+ size_reject_detail?: SizeRejectDetail;
22272
+ }
22273
+ interface XaiBox {
22274
+ x1?: number;
22275
+ y1?: number;
22276
+ x2?: number;
22277
+ y2?: number;
22278
+ }
22279
+ interface XaiCandidate {
22280
+ class?: number;
22281
+ name?: string;
22282
+ score?: number;
22283
+ }
22284
+ /** Per-detection top-k class scores; `index` references the inference `predictions[]`, not `annotations[]`. */
22285
+ interface XaiClassScore {
22286
+ index?: number;
22287
+ class?: number;
22288
+ name?: string;
22289
+ confidence?: number;
22290
+ box?: XaiBox;
22291
+ match_iou?: number;
22292
+ candidates?: XaiCandidate[];
22293
+ }
22294
+ interface XaiEmbeddingPoint {
22295
+ index?: number;
22296
+ class?: number;
22297
+ name?: string;
22298
+ confidence?: number;
22299
+ /** [0, 1] */
22300
+ x?: number;
22301
+ /** [0, 1] */
22302
+ y?: number;
22303
+ }
22304
+ interface XaiFeatureMap {
22305
+ /** Network stage, e.g. `stage12_C2f`. */
22306
+ stage?: string;
22307
+ /** Media ref of the stage grid JPEG. */
22308
+ media?: StringId;
22309
+ /** publicUrl snapshot of `media`. */
22310
+ url?: string;
22311
+ }
22312
+ /** One detection's own Grad-CAM map ("why THIS box, this class"). */
22313
+ interface XaiGradcamDetection {
22314
+ index?: number;
22315
+ class?: number;
22316
+ name?: string;
22317
+ confidence?: number;
22318
+ box?: XaiBox;
22319
+ match_iou?: number;
22320
+ /** Media ref of this detection's RGBA PNG map. */
22321
+ media?: StringId;
22322
+ /** publicUrl snapshot of `media`. */
22323
+ url?: string;
22324
+ width?: number;
22325
+ height?: number;
22326
+ }
22327
+ interface XaiEmbeddings {
22328
+ /** The projection that actually ran. */
22329
+ method?: "umap" | "tsne" | "pca" | "none";
22330
+ /** What the caller asked for. */
22331
+ requested_method?: "auto" | "umap" | "tsne" | "pca";
22332
+ layer?: string;
22333
+ embedding_dim?: number;
22334
+ points?: XaiEmbeddingPoint[];
22335
+ }
22336
+ interface XaiConfusionMatrix {
22337
+ url?: string;
22338
+ normalized_url?: string;
22339
+ source?: "training_artifacts";
22340
+ }
22341
+ /**
22342
+ * Explainable-AI results persisted on a group produced with `explain: true`.
22343
+ * Images live in media storage (refs + `*_url` publicUrl snapshots), never inline.
22344
+ * `computed_at` marks real persisted XAI (withheld when no image could be stored).
22345
+ */
22346
+ interface GroupXai {
22347
+ /** Which XAI parts were requested. */
22348
+ parts?: string[];
22349
+ /** Media ref of the EigenCAM RGBA PNG (alpha = activation). */
22350
+ heatmap_media?: StringId;
22351
+ heatmap_url?: string;
22352
+ /** e.g. `eigencam`. */
22353
+ heatmap_method?: string;
22354
+ heatmap_width?: number;
22355
+ heatmap_height?: number;
22356
+ /** Media ref of the gradient Grad-CAM RGBA PNG. */
22357
+ gradcam_media?: StringId;
22358
+ gradcam_url?: string;
22359
+ gradcam_width?: number;
22360
+ gradcam_height?: number;
22361
+ gradcam_per_detection?: XaiGradcamDetection[];
22362
+ feature_maps?: XaiFeatureMap[];
22363
+ class_scores?: XaiClassScore[];
22364
+ embeddings?: XaiEmbeddings;
22365
+ /** Training-time confusion-matrix image URLs of the model version. */
22366
+ confusion_matrix?: XaiConfusionMatrix;
22367
+ /** Reasons for any XAI part that could not be produced or persisted. */
22368
+ notes?: string[];
22369
+ /** Epoch ms when the XAI results were persisted. */
22370
+ computed_at?: number;
22371
+ }
22372
+ /** Per-group session inference metadata (session inference only). */
22373
+ interface SessionInference {
22374
+ /** Dominant shelf plane this frame contributed to. */
22375
+ plane_normal?: number[];
22376
+ /** This group provided the winning detection. */
22377
+ is_winner_in_cluster?: boolean;
22378
+ cross_frame_overlap_pct?: number;
22379
+ }
22380
+ interface AnnotationGroup {
22381
+ _id?: StringId;
22382
+ /** `ai-object-detection-model-version` `_id`. */
22383
+ model_version?: StringId;
22384
+ engine?: Engine;
22385
+ /** The zero-shot VLM used, e.g. `qwen/qwen3-vl-8b-instruct`. */
22386
+ zero_shot_model?: string;
22387
+ /** Opt-out training flag. Default true. */
22388
+ usable?: boolean;
22389
+ /** Epoch ms — set to now on create when missing. */
22390
+ time?: number;
22391
+ /** Epoch ms — server-stamped on every update. */
22392
+ edit_time?: number;
22393
+ annotation_state?: AnnotationState;
22394
+ /** Default false. Server-set true for `manual` / `auto_edited` groups. */
22395
+ confirmed?: boolean;
22396
+ /** Server-stamped from the JWT when the group is confirmed and does not already carry a valid confirmer. */
22397
+ confirmed_by?: AdminOrRep;
22398
+ /** Server-set to `model_version` for `auto` groups on create. */
22399
+ annotated_by_model_version_code?: StringId;
22400
+ annotations?: Annotation[];
22401
+ /** Link to the session's `inference_runs[]._id` (session inference only). */
22402
+ inference_run?: StringId;
22403
+ session_inference?: SessionInference;
22404
+ xai?: GroupXai;
22405
+ }
22406
+ interface TaskDataset {
22407
+ _id?: StringId;
22408
+ /** `ai-object-detection-dataset` `_id`. */
22409
+ dataset: StringId;
22410
+ subset: Subset;
22411
+ }
22412
+ interface Intrinsics {
22413
+ fx?: number;
22414
+ fy?: number;
22415
+ cx?: number;
22416
+ cy?: number;
22417
+ }
22418
+ interface Distortion {
22419
+ k1?: number;
22420
+ k2?: number;
22421
+ k3?: number;
22422
+ p1?: number;
22423
+ p2?: number;
22424
+ }
22425
+ interface Tracking {
22426
+ state?: "NORMAL" | "LIMITED" | "LOST";
22427
+ score?: number;
22428
+ drift_m?: number;
22429
+ velocity_mps?: number;
22430
+ }
22431
+ interface ImageStats {
22432
+ iso?: number;
22433
+ shutter?: number;
22434
+ lux?: number;
22435
+ /** Variance-of-Laplacian focus measure (higher = sharper). */
22436
+ sharpness?: number;
22437
+ }
22438
+ /** Depth-map presence + confidence fractions (election input). */
22439
+ interface DepthSummary {
22440
+ width?: number;
22441
+ height?: number;
22442
+ conf_high?: number;
22443
+ conf_medium?: number;
22444
+ conf_low?: number;
22445
+ }
22446
+ /** Per-frame AR context, present only on session frames. */
22447
+ interface FrameMeta {
22448
+ /** Session-scoped frame counter. */
22449
+ frame_id?: number;
22450
+ /** Device epoch ms. */
22451
+ ts?: number;
22452
+ /** 16 floats, 4x4 pose matrix, column-major. */
22453
+ pose?: number[];
22454
+ /** 3 floats, degrees. */
22455
+ euler_ypr?: number[];
22456
+ intrinsics?: Intrinsics;
22457
+ distortion?: Distortion;
22458
+ tracking?: Tracking;
22459
+ /** Shelf-distance gate (m) used when back-projecting this frame's detections. */
22460
+ distance_to_shelf_m?: number;
22461
+ /** CW degrees the sensor image was rotated to produce the stored image (0/90/180/270). */
22462
+ image_rotation_deg?: number;
22463
+ image_stats?: ImageStats;
22464
+ depth_summary?: DepthSummary;
22465
+ depth_source?: "lidar" | "estimated" | "none";
22466
+ /** Nearest depth sample, metres. */
22467
+ min_distance_depth?: number;
22468
+ /** Farthest depth sample, metres. */
22469
+ max_distance_depth?: number;
22470
+ /** Depth span (max − min), metres. */
22471
+ depth_variation?: number;
22472
+ /** Camera euler at capture, degrees. */
22473
+ yaw_degree?: number;
22474
+ pitch_degree?: number;
22475
+ roll_degree?: number;
22476
+ /** Variance-of-Laplacian focus measure. */
22477
+ frame_sharpness?: number;
22478
+ /** Device validator verdict for the frame at capture time. */
22479
+ capture_tier?: "good" | "warn" | "error";
22480
+ /** Epoch ms of the app's last successful detection-settings poll. */
22481
+ detection_settings_polled_at?: number;
22482
+ /** App build that captured the frame. */
22483
+ app_version?: string;
22484
+ }
22485
+ interface Data {
22486
+ _id: StringId;
22487
+ /** `media-storage` `_id` of the image. */
22488
+ file_media: StringId;
22489
+ shape?: number[];
22490
+ annotation_groups?: AnnotationGroup[];
22491
+ task_dataset?: TaskDataset[];
22492
+ /** Server-stamped on create. */
22493
+ creator?: AdminOrRep;
22494
+ /** Server-stamped on update. */
22495
+ editor?: AdminOrRep;
22496
+ /** Server-derived: true when any confirmed group has annotations. */
22497
+ annotated: boolean;
22498
+ /** Parent `ai-object-detection-session` `_id` (session frames only). */
22499
+ session?: StringId;
22500
+ frame_meta?: FrameMeta;
22501
+ /** Media `_id` of the raw float32 depth blob (session frames only). */
22502
+ depth_media?: StringId;
22503
+ /** Media `_id` of the raw uint8 confidence blob (session frames only). */
22504
+ confidence_media?: StringId;
22505
+ /** `[width, height]` of the depth blob. */
22506
+ depth_shape?: number[];
22507
+ disabled: boolean;
22508
+ company_namespace: string[];
22509
+ createdAt: Date;
22510
+ updatedAt: Date;
22511
+ }
22512
+ /**
22513
+ * Accepted `populatedKeys[]`. Top-level refs (`file_media`, `session`, `depth_media`,
22514
+ * `confidence_media`) land under `<field>_populated` while the ref stays an id;
22515
+ * nested paths (everything under `task_dataset.` / `annotation_groups.`) populate
22516
+ * INLINE, replacing the id with the document.
22517
+ */
22518
+ type PopulatedKeys = "file_media" | "task_dataset.dataset" | "annotation_groups.model_version" | "annotation_groups.annotations.label_id" | "annotation_groups.annotations.original_label" | "annotation_groups.annotated_by_model_version_code" | "annotation_groups.xai.heatmap_media" | "annotation_groups.xai.gradcam_media" | "annotation_groups.xai.gradcam_per_detection.media" | "annotation_groups.xai.feature_maps.media" | "session" | "depth_media" | "confidence_media";
22519
+ interface AnnotationPopulated extends Omit<Annotation, "label_id" | "original_label"> {
22520
+ label_id: StringId | AiObjectDetectionLabel.Data;
22521
+ original_label?: StringId | AiObjectDetectionLabel.Data;
22522
+ }
22523
+ interface GroupXaiPopulated extends Omit<GroupXai, "heatmap_media" | "gradcam_media" | "gradcam_per_detection" | "feature_maps"> {
22524
+ heatmap_media?: StringId | MediaStorage.MediaStorageSchema;
22525
+ gradcam_media?: StringId | MediaStorage.MediaStorageSchema;
22526
+ gradcam_per_detection?: (Omit<XaiGradcamDetection, "media"> & {
22527
+ media?: StringId | MediaStorage.MediaStorageSchema;
22528
+ })[];
22529
+ feature_maps?: (Omit<XaiFeatureMap, "media"> & {
22530
+ media?: StringId | MediaStorage.MediaStorageSchema;
22531
+ })[];
22532
+ }
22533
+ interface AnnotationGroupPopulated extends Omit<AnnotationGroup, "model_version" | "annotated_by_model_version_code" | "annotations" | "xai"> {
22534
+ model_version?: StringId | AiObjectDetectionModelVersion.Data;
22535
+ annotated_by_model_version_code?: StringId | AiObjectDetectionModelVersion.Data;
22536
+ annotations?: AnnotationPopulated[];
22537
+ xai?: GroupXaiPopulated;
22538
+ }
22539
+ interface TaskDatasetPopulated extends Omit<TaskDataset, "dataset"> {
22540
+ dataset: StringId | AiObjectDetectionDataset.Data;
22541
+ }
22542
+ /** Find / Get row: `Data` plus the `*_populated` companions and inline-populated nested refs (only when the matching `populatedKeys[]` was requested). */
22543
+ interface DataWithPopulatedKeys extends Omit<Data, "annotation_groups" | "task_dataset"> {
22544
+ annotation_groups?: AnnotationGroupPopulated[];
22545
+ task_dataset?: TaskDatasetPopulated[];
22546
+ file_media_populated?: MediaStorage.MediaStorageSchema;
22547
+ session_populated?: AiObjectDetectionSession.Data;
22548
+ depth_media_populated?: MediaStorage.MediaStorageSchema;
22549
+ confidence_media_populated?: MediaStorage.MediaStorageSchema;
22550
+ }
22551
+ /**
22552
+ * An annotation group as sent on create. `annotation_state` is REQUIRED — a group
22553
+ * without a valid one is rejected with 400 "Invalid annotation state". `manual` /
22554
+ * `auto_edited` are marked `confirmed` (+ `confirmed_by` from the JWT); `auto`
22555
+ * copies `model_version` into `annotated_by_model_version_code`. `time` defaults to now.
22556
+ */
22557
+ interface CreateAnnotationGroup extends Omit<AnnotationGroup, "annotation_state" | "confirmed_by" | "edit_time" | "annotated_by_model_version_code"> {
22558
+ annotation_state: AnnotationState;
22559
+ }
22560
+ interface CreateBody {
22561
+ /** Required — `media-storage` `_id` of the image. */
22562
+ file_media: StringId;
22563
+ shape?: number[];
22564
+ annotation_groups?: CreateAnnotationGroup[];
22565
+ task_dataset?: TaskDataset[];
22566
+ session?: StringId;
22567
+ frame_meta?: FrameMeta;
22568
+ depth_media?: StringId;
22569
+ confidence_media?: StringId;
22570
+ depth_shape?: number[];
22571
+ /** Optional tenant namespace override for SDK callers (otherwise injected from the session). */
22572
+ company_namespace?: string[];
22573
+ }
22574
+ /**
22575
+ * PUT body. `annotation_groups` REPLACES the stored array wholesale — resend every
22576
+ * group to keep (with its `_id`). `editor` is server-stamped, `annotated` re-derived,
22577
+ * `edit_time` restamped per group; a confirmed group keeps its existing `confirmed_by`.
22578
+ * Human (`manual` / `auto_edited`) boxes whose geometry changed are re-placed in
22579
+ * world coordinates on save (session frames), and a real content change schedules
22580
+ * a session re-analysis. Set `disabled: true` to soft-delete.
22581
+ */
22582
+ type UpdateBody = Partial<Omit<Data, "_id" | "createdAt" | "updatedAt" | "company_namespace" | "creator" | "editor" | "annotated">>;
22583
+ /** Filters honoured by find (and by the bulk patch). Date bounds are epoch ms (or date strings). */
22584
+ interface FilterParams {
22585
+ _id?: StringId | StringId[];
22586
+ /** Tasks (frames) belonging to a session `_id`. */
22587
+ session?: StringId | StringId[];
22588
+ /** Session-scoped frame counter. */
22589
+ "frame_meta.frame_id"?: number | number[];
22590
+ /** Tasks belonging to a dataset `_id`. */
22591
+ "task_dataset.dataset"?: StringId | StringId[];
22592
+ /** Whether the task has any confirmed annotations. */
22593
+ annotated?: boolean;
22594
+ /** Omitted / false = active tasks only; true = disabled tasks only. */
22595
+ disabled?: boolean;
22596
+ from_createdAt?: number;
22597
+ to_createdAt?: number;
22598
+ from_updatedAt?: number;
22599
+ to_updatedAt?: number;
22600
+ }
22601
+ namespace Find {
22602
+ type Params = DefaultPaginationQueryParams & FilterParams & {
22603
+ /** Only tasks with at least one annotation (in any group) referencing the given label id(s) — surfaces candidate crops for a label's reference photo. */
22604
+ annotated_label?: StringId | StringId[];
22605
+ /** Mutually-exclusive review status filter (applied before counting / pagination). Invalid values → 400. */
22606
+ annotation_status?: AnnotationStatus;
22607
+ populatedKeys?: PopulatedKeys[];
22608
+ };
22609
+ interface Result extends DefaultPaginationResult {
22610
+ data: DataWithPopulatedKeys[];
22611
+ }
22612
+ }
22613
+ namespace Get {
22614
+ type ID = StringId;
22615
+ interface Params {
22616
+ populatedKeys?: PopulatedKeys[];
22617
+ }
22618
+ /** 400 (not 404) when the id is unknown in the caller's namespace. */
22619
+ type Result = DataWithPopulatedKeys;
22620
+ }
22621
+ namespace Create {
22622
+ type Body = CreateBody;
22623
+ type Result = Data;
22624
+ }
22625
+ namespace Update {
22626
+ type ID = StringId;
22627
+ type Body = UpdateBody;
22628
+ type Result = Data;
22629
+ }
22630
+ namespace Patch {
22631
+ /** Query filters selecting the tasks to bulk-update (e.g. `{ session }` for "approve all auto"). No pagination / population. */
22632
+ type Params = FilterParams;
22633
+ interface WriteQuery {
22634
+ /** Dotted document path, e.g. `annotation_groups.0.confirmed`. */
22635
+ key: string;
22636
+ /** `set` → `$set`; `addToSet` → `$addToSet: { $each: value }`; `pull` → `$pull: { $in: value }`. */
22637
+ command: "set" | "addToSet" | "pull";
22638
+ value: any;
22639
+ }
22640
+ type Body = {
22641
+ writeQuery: WriteQuery[];
22642
+ };
22643
+ interface Result {
22644
+ /** Matched documents. */
22645
+ nFound: number;
22646
+ /** Modified documents. */
22647
+ nModified: number;
22648
+ }
22649
+ }
22650
+ namespace Remove {
22651
+ type ID = StringId;
22652
+ /** The task after soft-deletion (`disabled: true`). */
22653
+ type Result = Data;
22654
+ }
22655
+ }
22656
+ namespace AiObjectDetectionSession {
22657
+ /** Lifecycle FSM. `uploaded` is written by the device's upload-complete marker, `infer_in_progress`/`inferred`/`failed` by analysis runs; `rearbitrating` is reserved/legacy (no writer). */
22658
+ type SessionStatus = "open" | "uploaded" | "infer_in_progress" | "inferred" | "rearbitrating" | "failed";
22659
+ /** Articulated election verdict derived from `session_score` against the namespace detection-settings score bands. */
22660
+ type SessionVerdict = "excellent" | "good" | "acceptable" | "rejected";
22661
+ interface Device {
22662
+ platform?: string;
22663
+ os?: string;
22664
+ model?: string;
22665
+ app_version?: string;
22666
+ ar_engine?: "ARKit" | "ARCore";
22667
+ }
22668
+ interface CaptureSettings {
22669
+ /** AR frame capture rate the app was set to (Hz). */
22670
+ rate_hz?: number;
22671
+ recording_enabled?: boolean;
22672
+ target_distance_m?: number;
22673
+ /** Camera format preference active during capture. */
22674
+ resolution?: "medium" | "high" | "max" | string;
22675
+ }
22676
+ /** Floor lock measured on-device at capture entry; absent when the rep skipped the floor point. Height above floor of any world point = y - y_world. */
22677
+ interface Ground {
22678
+ /** Floor height (world Y), metres. */
22679
+ y_world?: number;
22680
+ /** Gated depth samples behind the median. */
22681
+ samples?: number;
22682
+ /** Interquartile spread of the samples, metres (lock quality). */
22683
+ spread_m?: number;
22684
+ /** Camera height above the floor at lock time, metres. */
22685
+ camera_height_m?: number;
22686
+ }
22687
+ /** A frame excluded from an election because it violated the error-tier limits. */
22688
+ interface ElectionExcluded {
22689
+ task?: StringId;
22690
+ /** e.g. `sharpness`, `tracking`, `too_near`, `too_far`, `yaw_delta`, `pitch_delta`, `roll_delta`. */
22691
+ violations?: string[];
22692
+ }
22693
+ /** Legacy scene plane (from the removed in-session build_scene flow). */
22694
+ interface Plane {
22695
+ _id?: StringId;
22696
+ kind?: "shelf" | "floor" | "wall";
22697
+ /** 3-float plane normal. */
22698
+ normal?: number[];
22699
+ /** Plane equation constant in Ax+By+Cz+d=0. */
22700
+ d?: number;
22701
+ inliers?: number;
22702
+ bounds?: {
22703
+ min?: number[];
22704
+ max?: number[];
22705
+ };
22706
+ }
22707
+ interface PointCloudStats {
22708
+ n_points?: number;
22709
+ voxel_size_m?: number;
22710
+ bbox_min?: number[];
22711
+ bbox_max?: number[];
22712
+ }
22713
+ /** Legacy scene-level outputs; retained for older data, no longer written. */
22714
+ interface Scene {
22715
+ planes?: Plane[];
22716
+ /** `media.mediaStorages` id of the .ply / .npy point cloud. */
22717
+ point_cloud_media?: StringId;
22718
+ point_cloud_stats?: PointCloudStats;
22719
+ /** Coordinate system the world positions are reported in. */
22720
+ world_frame?: "arkit" | "arcore" | "normalized";
22721
+ }
22722
+ /** Legacy cross-frame fused object (fused output now lives on `ai-object-detection-session-analysis`). */
22723
+ interface SessionObject {
22724
+ _id?: StringId;
22725
+ label_id?: StringId;
22726
+ /** How many frames contributed to this object. */
22727
+ cluster_size?: number;
22728
+ world_position?: {
22729
+ x?: number;
22730
+ y?: number;
22731
+ z?: number;
22732
+ };
22733
+ world_orientation?: {
22734
+ yaw?: number;
22735
+ pitch?: number;
22736
+ roll?: number;
22737
+ };
22738
+ bbox_3d?: {
22739
+ min?: number[];
22740
+ max?: number[];
22741
+ };
22742
+ /** Fused 0..1 placement confidence. */
22743
+ placement_confidence?: number;
22744
+ winning_task?: StringId;
22745
+ winning_annotation_id?: StringId;
22746
+ contributing_tasks?: StringId[];
22747
+ /** Weights / tiebreak rationale. */
22748
+ arbitration?: any;
22749
+ }
22750
+ /** Legacy per-run inference record (analysis runs are now their own documents). */
22751
+ interface InferenceRun {
22752
+ _id?: StringId;
22753
+ model_version?: StringId;
22754
+ started_at?: number;
22755
+ finished_at?: number;
22756
+ /** Clustering params, RANSAC config. */
22757
+ config?: any;
22758
+ status?: "pending" | "success" | "failed";
22759
+ triggered_by?: AdminOrRep;
22760
+ notes?: string;
22761
+ }
22762
+ interface Data {
22763
+ _id: StringId;
22764
+ company_namespace: string[];
22765
+ disabled: boolean;
22766
+ /** Device-generated session id (4-char); unique per namespace among active rows. Election clones carry a `-E<n>` suffix. */
22767
+ session_id?: string;
22768
+ /** Provenance: set when this session was materialized from an election over another session's frames. */
22769
+ source_session?: StringId;
22770
+ device?: Device;
22771
+ /** `clients` _id of the scanned store (stamped by the frame intake). */
22772
+ client?: StringId;
22773
+ /** `ai-object-detection-category` _id picked on the device; drives the auto-analysis fired by the upload-complete marker. */
22774
+ category?: StringId;
22775
+ /** `ai-object-detection-mission` _id the session was started FROM ("SCAN THIS MISSION"); attribution anchor for mission results. */
22776
+ mission?: StringId;
22777
+ /** `representatives` _id sent on the frames (defaults to the rep in the token). */
22778
+ rep?: StringId;
22779
+ /** DEVICE visit id (`visits.visit_id`) the scan happened in — stored as-is, never resolved to a server ref. */
22780
+ visit_id?: string;
22781
+ /** `sv.routes` _id of the visit's route. */
22782
+ route?: StringId;
22783
+ /** Business day of the scan, `YYYY-MM-DD` — as sent by the device, else stamped once from the capture time. */
22784
+ business_day?: string;
22785
+ /** IANA timezone of the device at capture. */
22786
+ time_zone?: string;
22787
+ capture_settings?: CaptureSettings;
22788
+ /** Device-computed swept shelf area, m² (includes quality-rejected attempts, so it cannot be recomputed server-side). */
22789
+ coverage_m2?: number;
22790
+ ground?: Ground;
22791
+ /** Camera height above the locked floor while shooting, metres (the rep's standing eye level). Server self-heals it from poses when missing. */
22792
+ eye_level_m?: number;
22793
+ /** The rep tapped Skip on the floor point (deliberate skip vs a lock that never converged). */
22794
+ ground_skipped?: boolean;
22795
+ /** 'gravity' on both ARKit/ARCore — recorded, not assumed. */
22796
+ world_alignment?: string;
22797
+ /** Election evaluation: average frame quality, 0..1. */
22798
+ session_score?: number;
22799
+ session_verdict?: SessionVerdict;
22800
+ /** e.g. `coverage_below_target`, `jump_detected`, `too_many_elected`. */
22801
+ rejection_reasons?: string[];
22802
+ election_excluded?: ElectionExcluded[];
22803
+ status: SessionStatus;
22804
+ _errors?: any[];
22805
+ /** Incremented by the frame intake. */
22806
+ frames_total: number;
22807
+ /** Frames that passed validation (incremented by the frame intake). */
22808
+ frames_accepted: number;
22809
+ /** Frames materialized as tasks (incremented by the frame intake). */
22810
+ tasks_count: number;
22811
+ /** Refreshed on every successful analysis run. */
22812
+ detections_count: number;
22813
+ /** Refreshed on every successful analysis run. */
22814
+ objects_count: number;
22815
+ scene?: Scene;
22816
+ objects?: SessionObject[];
22817
+ inference_runs?: InferenceRun[];
22818
+ /** The rep/admin who scanned (server-stamped on create). */
22819
+ creator?: AdminOrRep;
22820
+ /** Server-stamped on update. */
22821
+ editor?: AdminOrRep;
22822
+ createdAt: Date;
22823
+ updatedAt: Date;
22824
+ }
22825
+ type PopulatedKeys = "client" | "category" | "inference_runs.model_version" | "objects.label_id" | "scene.point_cloud_media";
22826
+ interface InferenceRunWithPopulatedKeys extends Omit<InferenceRun, "model_version"> {
22827
+ /** Populated in place when `inference_runs.model_version` is requested. */
22828
+ model_version?: StringId | AiObjectDetectionModelVersion.Data;
22829
+ }
22830
+ interface SessionObjectWithPopulatedKeys extends Omit<SessionObject, "label_id"> {
22831
+ /** Populated in place when `objects.label_id` is requested. */
22832
+ label_id?: StringId | AiObjectDetectionLabel.Data;
22833
+ }
22834
+ interface SceneWithPopulatedKeys extends Omit<Scene, "point_cloud_media"> {
22835
+ /** Populated in place when `scene.point_cloud_media` is requested. */
22836
+ point_cloud_media?: StringId | MediaStorage.MediaStorageSchema;
22837
+ }
22838
+ /** `client` / `category` keep their ids and add `<key>_populated`; the nested keys are populated in place. */
22839
+ interface DataWithPopulatedKeys extends Omit<Data, "inference_runs" | "objects" | "scene"> {
22840
+ client_populated?: Client.ClientSchema;
22841
+ category_populated?: AiObjectDetectionCategory.Data;
22842
+ inference_runs?: InferenceRunWithPopulatedKeys[];
22843
+ objects?: SessionObjectWithPopulatedKeys[];
22844
+ scene?: SceneWithPopulatedKeys;
22845
+ }
22846
+ /** Explicit/manual session creation (sessions are normally upserted by the frame intake). `creator` and counters are server-managed. */
22847
+ interface CreateBody {
22848
+ session_id?: string;
22849
+ source_session?: StringId;
22850
+ device?: Device;
22851
+ client?: StringId;
22852
+ category?: StringId;
22853
+ mission?: StringId;
22854
+ rep?: StringId;
22855
+ visit_id?: string;
22856
+ route?: StringId;
22857
+ business_day?: string;
22858
+ time_zone?: string;
22859
+ capture_settings?: CaptureSettings;
22860
+ coverage_m2?: number;
22861
+ ground?: Ground;
22862
+ eye_level_m?: number;
22863
+ ground_skipped?: boolean;
22864
+ world_alignment?: string;
22865
+ /** Defaults to `open`. */
22866
+ status?: SessionStatus;
22867
+ company_namespace?: string[];
22868
+ }
22869
+ /** PUT accepts any stored field; `editor` is server-stamped. Set `disabled: true` to soft-delete. */
22870
+ type UpdateBody = Partial<Omit<Data, "_id" | "createdAt" | "updatedAt" | "company_namespace" | "creator" | "editor">>;
22871
+ /** Filter keys honoured by `find` and by the bulk `patch`. */
22872
+ type FilterParams = {
22873
+ _id?: StringId | StringId[];
22874
+ session_id?: string | string[];
22875
+ status?: SessionStatus | SessionStatus[];
22876
+ client?: StringId | StringId[];
22877
+ category?: StringId | StringId[];
22878
+ rep?: StringId | StringId[];
22879
+ visit_id?: string | string[];
22880
+ route?: StringId | StringId[];
22881
+ "creator._id"?: StringId | StringId[];
22882
+ "inference_runs.model_version"?: StringId | StringId[];
22883
+ from_updatedAt?: number;
22884
+ to_updatedAt?: number;
22885
+ from_createdAt?: number;
22886
+ to_createdAt?: number;
22887
+ /** Regex on `name` — sessions carry no `name`, so this matches nothing; listed only because the shared query helper honours it. */
22888
+ search?: string;
22889
+ /** Include disabled (soft-deleted) sessions. */
22890
+ disabled?: boolean;
22891
+ };
22892
+ interface WriteQuery {
22893
+ key: string;
22894
+ command: "set" | "addToSet" | "pull";
22895
+ value: any;
22896
+ }
22897
+ namespace Find {
22898
+ type Params = DefaultPaginationQueryParams & FilterParams & {
22899
+ populatedKeys?: PopulatedKeys[];
22900
+ };
22901
+ interface Result extends DefaultPaginationResult {
22902
+ data: DataWithPopulatedKeys[];
22903
+ }
22904
+ }
22905
+ namespace Get {
22906
+ type ID = StringId;
22907
+ interface Params {
22908
+ populatedKeys?: PopulatedKeys[];
22909
+ }
22910
+ type Result = DataWithPopulatedKeys;
22911
+ }
22912
+ namespace Create {
22913
+ type Body = CreateBody;
22914
+ type Result = Data;
22915
+ }
22916
+ namespace Update {
22917
+ type ID = StringId;
22918
+ type Body = UpdateBody;
22919
+ type Result = Data;
22920
+ }
22921
+ /** Bulk update: `writeQuery[]` is applied to every session matching the query filters. */
22922
+ namespace Patch {
22923
+ type Params = FilterParams;
22924
+ type Body = {
22925
+ writeQuery: WriteQuery[];
22926
+ };
22927
+ type Result = {
22928
+ nFound: number;
22929
+ nModified: number;
22930
+ };
22931
+ }
22932
+ namespace Remove {
22933
+ type ID = StringId;
22934
+ interface Params {
22935
+ /** When `true`, also soft-delete the session's child tasks (default: tasks are kept as training data). */
22936
+ cascade?: boolean;
22937
+ }
22938
+ type Result = Data;
22939
+ }
22940
+ }
22941
+ namespace ActivityAiObjectDetectionSessionFrame {
22942
+ interface Device {
22943
+ platform?: string;
22944
+ os?: string;
22945
+ model?: string;
22946
+ app_version?: string;
22947
+ ar_engine?: "ARKit" | "ARCore";
22948
+ }
22949
+ interface CaptureSettings {
22950
+ rate_hz?: number;
22951
+ recording_enabled?: boolean;
22952
+ target_distance_m?: number;
22953
+ resolution?: "medium" | "high" | "max" | string;
22954
+ }
22955
+ /** Floor lock from the capture entry phase (skippable on the device). */
22956
+ interface Ground {
22957
+ y_world?: number;
22958
+ samples?: number;
22959
+ spread_m?: number;
22960
+ camera_height_m?: number;
22961
+ }
22962
+ /** Per-frame AR metadata carried in `meta_inline` (JSON). Fields map onto the materialized task's `frame_meta`; camelCase device aliases (`trackingScore`, `eulerYPR`, `imageStats`, `driftM`, `velocityMps`, `imageRotationDeg`, `depth.{width,height,minM,maxM,distanceToShelfM,confidence}`) are also tolerated. */
22963
+ interface MetaInline {
22964
+ /** Device-generated session id (4-char). Required here or as the top-level `session_id`. */
22965
+ session_id?: string;
22966
+ /** Fallback `media.mediaStorages` _id of the frame image when not sent top-level. */
22967
+ media_id?: string;
22968
+ /** Session-scoped frame counter. */
22969
+ frame_id?: number;
22970
+ /** Device epoch (ms). */
22971
+ ts?: number;
22972
+ /** 16-float 4x4 pose matrix, column-major. */
22973
+ pose?: number[];
22974
+ /** 3-float yaw/pitch/roll in degrees. */
22975
+ euler_ypr?: number[];
22976
+ intrinsics?: {
22977
+ fx: number;
22978
+ fy: number;
22979
+ cx: number;
22980
+ cy: number;
22981
+ };
22982
+ distortion?: {
22983
+ k1?: number;
22984
+ k2?: number;
22985
+ k3?: number;
22986
+ p1?: number;
22987
+ p2?: number;
22988
+ };
22989
+ tracking?: {
22990
+ state?: "NORMAL" | "LIMITED" | "LOST";
22991
+ /** 0..1 (a 0..100 device value is normalized). */
22992
+ score?: number;
22993
+ drift_m?: number;
22994
+ velocity_mps?: number;
22995
+ };
22996
+ /** Shelf-distance gate (metres) captured at the frame. */
22997
+ distance_to_shelf_m?: number;
22998
+ image_stats?: {
22999
+ iso?: number;
23000
+ shutter?: number;
23001
+ lux?: number;
23002
+ /** Variance-of-Laplacian focus measure; -1 = unmeasured. */
23003
+ sharpness?: number;
23004
+ };
23005
+ /** Depth-map summary from the device (width/height + confidence fractions persist on `frame_meta.depth_summary`). */
23006
+ depth?: {
23007
+ width?: number;
23008
+ height?: number;
23009
+ minM?: number;
23010
+ maxM?: number;
23011
+ distanceToShelfM?: number;
23012
+ confidence?: {
23013
+ high?: number;
23014
+ medium?: number;
23015
+ low?: number;
23016
+ };
23017
+ };
23018
+ depth_source?: "lidar" | "estimated" | "none";
23019
+ /** [width, height] of the depth blob. */
23020
+ depth_shape?: number[];
23021
+ /** [width, height] of the image. */
23022
+ image_shape?: number[];
23023
+ /** Nearest depth sample in the frame, metres (app >= 0.39.0). */
23024
+ min_distance_depth?: number;
23025
+ /** Farthest depth sample in the frame, metres. */
23026
+ max_distance_depth?: number;
23027
+ /** Depth span (max - min), metres. */
23028
+ depth_variation?: number;
23029
+ yaw_degree?: number;
23030
+ pitch_degree?: number;
23031
+ roll_degree?: number;
23032
+ /** Variance-of-Laplacian focus measure (higher = sharper). */
23033
+ frame_sharpness?: number;
23034
+ /** Device validator verdict for the frame at capture time. */
23035
+ capture_tier?: "good" | "warn" | "error";
23036
+ /** Epoch ms of the app's last successful detection-settings poll before this frame. */
23037
+ detection_settings_polled_at?: number;
23038
+ /** Degrees the sensor image was rotated CW to produce the stored image. */
23039
+ image_rotation_deg?: number;
23040
+ /** Applied to the session on first frame; `app_version` is also persisted per frame. */
23041
+ device?: Device;
23042
+ /** Applied to the session on first frame. */
23043
+ capture_settings?: CaptureSettings;
23044
+ /** Device-computed swept shelf area for the whole session so far, m² (upserted onto the session via $max). */
23045
+ coverage_m2?: number;
23046
+ /** 'gravity' on both ARKit and ARCore. */
23047
+ world_alignment?: string;
23048
+ ground?: Ground;
23049
+ /** Camera height above the locked floor while shooting, metres. */
23050
+ eye_level_m?: number;
23051
+ /** True when the rep tapped Skip on the floor point. */
23052
+ ground_skipped?: boolean;
23053
+ /** `ai-object-detection-category` _id picked on the device (non-fatal when it no longer exists). */
23054
+ category?: string;
23055
+ /** `ai-object-detection-mission` _id the session was started FROM (non-fatal when it no longer exists). */
23056
+ mission?: string;
23057
+ /** Alias of the top-level `visit_id` (the top-level field wins). */
23058
+ visit_id?: string;
23059
+ /** Alias of the top-level `route`. */
23060
+ route?: string;
23061
+ /** Alias of the top-level `business_day`. */
23062
+ business_day?: string;
23063
+ /** Alias of the top-level `time_zone`. */
23064
+ time_zone?: string;
23065
+ /** Completion marker — a frameless POST sent once the upload queue drains. */
23066
+ session_complete?: boolean | string;
23067
+ }
23068
+ interface FrameGeoTag {
23069
+ lat: number;
23070
+ lng: number;
23071
+ formatted_address?: string;
23072
+ extra?: any;
23073
+ }
23074
+ /** The stored activity frame document (collection `ai.objectDetectionSessionFrames`). The image, `frame_meta` and depth/confidence media live on the referenced `task`. Not readable through this service — query tasks by `?session=` instead. */
23075
+ interface Data {
23076
+ _id: StringId;
23077
+ company_namespace: string[];
23078
+ /** `clients` _id the captured shelf belongs to. */
23079
+ client: StringId;
23080
+ /** `representatives` _id of the capturing rep. */
23081
+ rep?: StringId;
23082
+ /** Parent `ai-object-detection-session` _id. */
23083
+ session: StringId;
23084
+ /** Device-generated session id. */
23085
+ session_id?: string;
23086
+ /** The materialized `ai-object-detection-task` _id. */
23087
+ task?: StringId;
23088
+ /** DEVICE visit id (`visits.visit_id`), stored as-is. */
23089
+ visit_id?: string;
23090
+ /** `sv.routes` _id. */
23091
+ route?: StringId;
23092
+ /** `YYYY-MM-DD`. */
23093
+ business_day?: string;
23094
+ time_zone?: string;
23095
+ geo_tag?: FrameGeoTag;
23096
+ /** Device epoch ms of the capture. */
23097
+ time?: number;
23098
+ creator?: AdminOrRep;
23099
+ createdAt: Date;
23100
+ updatedAt: Date;
23101
+ }
23102
+ /** JSON-equivalent of the multipart frame intake. For a FRAME post `client`, `media_id` (top-level or in `meta_inline`) and `session_id` (top-level or in `meta_inline`) are required; for the COMPLETION marker only `session_id` + `session_complete: true`. */
23103
+ interface CreateBody {
23104
+ /** `media.mediaStorages` _id of the pre-uploaded RGB frame image (becomes the task's `file_media`). */
23105
+ media_id?: StringId;
23106
+ /** Fallback device session id when not present inside `meta_inline`. */
23107
+ session_id?: string;
23108
+ /** Per-frame AR metadata — JSON string or already-parsed object. */
23109
+ meta_inline?: string | MetaInline;
23110
+ /** Alias for `meta_inline`. */
23111
+ meta?: string | MetaInline;
23112
+ /** `clients` _id the captured shelf belongs to (must exist and not be disabled in the namespace). */
23113
+ client?: StringId;
23114
+ /** `representatives` _id of the capturing rep; defaults to the rep in the token. */
23115
+ rep?: StringId;
23116
+ /** DEVICE visit id (`visits.visit_id`) the scan happened in — stored as-is on the frame and the parent session. */
23117
+ visit_id?: string;
23118
+ /** `sv.routes` _id of the visit's route. */
23119
+ route?: StringId;
23120
+ /** `YYYY-MM-DD` (rejected otherwise). When absent the session is stamped once from the capture time under the rep's stamping context. */
23121
+ business_day?: string;
23122
+ /** IANA timezone of the device at capture. */
23123
+ time_zone?: string;
23124
+ /** `{ lat, lng, formatted_address? }` — JSON string in multipart bodies; `lat`/`lng` must be numeric. */
23125
+ geo_tag?: string | {
23126
+ lat?: number;
23127
+ lng?: number;
23128
+ formatted_address?: string;
23129
+ };
23130
+ /** Device epoch ms of the capture. */
23131
+ time?: number | string;
23132
+ /** Completion-marker mode (alias of `meta_inline.session_complete`). */
23133
+ session_complete?: boolean | string;
23134
+ company_namespace?: string[];
23135
+ }
23136
+ /** Response of a frame post. */
23137
+ interface FrameResult {
23138
+ /** The parent session `_id`. */
23139
+ session: StringId;
23140
+ session_id: string;
23141
+ /** The materialized task `_id`. */
23142
+ task: StringId;
23143
+ /** The activity frame document `_id`. */
23144
+ frame: StringId;
23145
+ frame_id?: number;
23146
+ client: StringId;
23147
+ /** Media id of the frame image (= `media_id`). */
23148
+ file_media: StringId;
23149
+ /** Media id of the uploaded depth blob, if a `depth` file part was sent. */
23150
+ depth_media?: StringId;
23151
+ /** Media id of the uploaded confidence blob, if a `confidence` file part was sent. */
23152
+ confidence_media?: StringId;
23153
+ }
23154
+ /** Whether the category auto-analysis was fired by the completion marker. */
23155
+ interface AutoAnalysisResult {
23156
+ triggered: boolean;
23157
+ /** Present when not triggered: `no_category`, `category_has_no_model_settings`, `session_verdict_rejected`, `error`. */
23158
+ reason?: string;
23159
+ category?: StringId;
23160
+ /** Number of category `model_settings` items queued (run serially in the background). */
23161
+ runs?: number;
23162
+ }
23163
+ /** Response of a `session_complete: true` (frameless) post. */
23164
+ interface SessionCompleteResult {
23165
+ session: StringId;
23166
+ session_id: string;
23167
+ status: "uploaded";
23168
+ auto_analysis: AutoAnalysisResult;
23169
+ }
23170
+ namespace Create {
23171
+ type Body = CreateBody;
23172
+ type Result = FrameResult | SessionCompleteResult;
23173
+ }
23174
+ }
23175
+ namespace AiObjectDetectionSessionAnalysis {
23176
+ /** `pending` (opened) → `in_progress` (background job started) → `success` | `failed` (`_errors`). */
23177
+ type AnalysisStatus = "pending" | "in_progress" | "success" | "failed";
23178
+ /** Fusion / composition tuning knobs (`SceneMathConfig`). Every key is optional — omit for the server defaults. */
23179
+ interface SceneMathConfig {
23180
+ /** Run-level alias of the top-level `inference_concurrency` (1..10, default 4); the top-level field wins. */
23181
+ inference_concurrency?: number;
23182
+ /** Base world distance to merge detections into one object (default 0.08 m). */
23183
+ cluster_eps_m?: number;
23184
+ /** Extra merge radius when labels match (default 0.02 m). */
23185
+ class_agree_bonus_m?: number;
23186
+ /** A scene object absorbs at most ONE detection per frame (default true). */
23187
+ block_same_frame?: boolean;
23188
+ /** Ignore depth below this (default 0.05 m). */
23189
+ min_depth_m?: number;
23190
+ /** Ignore depth above this (default 6 m). */
23191
+ max_depth_m?: number;
23192
+ /** Min ARKit depth-confidence to keep a pixel: 0/1/2 (default 1). */
23193
+ conf_threshold?: number;
23194
+ /** Percentile of bbox depths to take, front-biased (default 30). */
23195
+ front_percentile?: number;
23196
+ /** Depth gate slack beyond `distance_to_shelf_m` (default 0.30 m). */
23197
+ shelf_tolerance_m?: number;
23198
+ /** Same-frame gate: same-label 2D IoU at/above this = duplicate box (default 0.92). */
23199
+ same_frame_iou_thresh?: number;
23200
+ /** Same-frame gate: same-label world distance below this = same spot (default 0.02 m). */
23201
+ same_frame_min_separation_m?: number;
23202
+ /** Same-frame gate: drop detections under this detector confidence (default 0 = off). */
23203
+ min_detection_confidence?: number;
23204
+ /** Same-frame gate: drop implausibly small front faces (default 0.5 cm). */
23205
+ min_object_size_cm?: number;
23206
+ /** Same-frame gate: drop implausibly large front faces (default 500 cm). */
23207
+ max_object_size_cm?: number;
23208
+ /** Cross-frame matcher: consensus-plane projection matching (default true). */
23209
+ plane_merge?: boolean;
23210
+ /** Min projected-rect IoU to merge (default 0.1). */
23211
+ plane_merge_iou?: number;
23212
+ /** Max plane-depth difference to merge, metres (default 0.25). */
23213
+ plane_merge_depth_delta_m?: number;
23214
+ /** Dims reclassifier master switch (default false). */
23215
+ reclassify_labels?: boolean;
23216
+ reclassify_keep_dev?: number;
23217
+ reclassify_target_dev?: number;
23218
+ reclassify_min_margin?: number;
23219
+ reclassify_conf_margin_scale?: number;
23220
+ reclassify_weight_scale?: number;
23221
+ reclassify_weight_aspect?: number;
23222
+ reclassify_min_depth_confidence?: number;
23223
+ /** Post-walk merge of overlapping same-group clusters (default true; only with `reclassify_labels`). */
23224
+ group_consensus_merge?: boolean;
23225
+ /** Physical size gate master switch (default true). */
23226
+ size_gate?: boolean;
23227
+ size_gate_dims_allowance?: number;
23228
+ size_gate_area_allowance?: number;
23229
+ size_gate_min_depth_confidence?: number;
23230
+ /** Shelf composition master switch (default true). */
23231
+ shelf_analysis?: boolean;
23232
+ shelf_support_min_overlap?: number;
23233
+ shelf_gap_split_m?: number;
23234
+ shelf_min_spacing_m?: number;
23235
+ shelf_stack_max_penetration_m?: number;
23236
+ shelf_row_split_m?: number;
23237
+ shelf_min_stacks?: number;
23238
+ /** Build + store the voxel point cloud and RANSAC shelf planes (default true). */
23239
+ build_point_cloud?: boolean;
23240
+ pc_stride?: number;
23241
+ pc_voxel_size_m?: number;
23242
+ ransac_max_planes?: number;
23243
+ ransac_min_inlier_ratio?: number;
23244
+ ransac_distance_thresh_m?: number;
23245
+ }
23246
+ interface Vec3 {
23247
+ x?: number;
23248
+ y?: number;
23249
+ z?: number;
23250
+ }
23251
+ /** 2D box (YOLO-normalized cx, cy, w, h as stored by the backend). */
23252
+ interface Box2D {
23253
+ x1?: number;
23254
+ y1?: number;
23255
+ x2?: number;
23256
+ y2?: number;
23257
+ }
23258
+ interface ArbitrationScore {
23259
+ task_id?: StringId;
23260
+ annotation_id?: StringId;
23261
+ frame_id?: number;
23262
+ score?: number;
23263
+ winner?: boolean;
23264
+ /** Six normalized inputs of the weighted view score. */
23265
+ inputs?: {
23266
+ det?: number;
23267
+ depth?: number;
23268
+ track?: number;
23269
+ center?: number;
23270
+ size?: number;
23271
+ agree?: number;
23272
+ };
23273
+ }
23274
+ /** Winner-selection rationale (mixed-type field on the model). */
23275
+ interface Arbitration {
23276
+ strategy?: string;
23277
+ weights?: {
23278
+ [factor: string]: number;
23279
+ };
23280
+ scores?: ArbitrationScore[];
23281
+ [key: string]: any;
23282
+ }
23283
+ /** A concluded object on the shelf (one per cross-frame cluster). */
23284
+ interface AnalysisObject {
23285
+ /** Contributing task annotations carry this id in their `cluster_id` after the run succeeds. */
23286
+ _id: StringId;
23287
+ label_id?: StringId;
23288
+ label_name?: string;
23289
+ /** `kept` = single facing, `merged` = several facings fused. */
23290
+ state?: "kept" | "merged";
23291
+ /** Fused placement confidence, 0..1. */
23292
+ confidence?: number;
23293
+ /** Centroid in world coordinates, metres. */
23294
+ world?: Vec3;
23295
+ /** Physical front-face size, centimetres. */
23296
+ size?: {
23297
+ w?: number;
23298
+ h?: number;
23299
+ };
23300
+ /** Distance from the camera, metres. */
23301
+ depth?: number;
23302
+ /** Metres above the device-locked floor; null when the rep skipped the floor point. */
23303
+ height_above_ground_m?: number | null;
23304
+ /** Facing rotation about world-Y, radians; null when the winning task lacks a pose. */
23305
+ yaw?: number | null;
23306
+ /** How many facings/frames contributed. */
23307
+ cluster_size?: number;
23308
+ bbox_3d?: {
23309
+ min?: number[];
23310
+ max?: number[];
23311
+ };
23312
+ winning_task?: StringId;
23313
+ winning_annotation_id?: StringId;
23314
+ /** Winner's 2D crop region on `winning_image_media`. */
23315
+ winning_box?: Box2D;
23316
+ /** `media.mediaStorages` id of the winning frame image (crop source). */
23317
+ winning_image_media?: StringId;
23318
+ contributing_tasks?: StringId[];
23319
+ arbitration?: Arbitration;
23320
+ }
23321
+ /** Per-label conclusion — detection counts by outcome. */
23322
+ interface ConcludedLabel {
23323
+ _id?: StringId;
23324
+ label_id?: StringId;
23325
+ label_name?: string;
23326
+ /** Detections placed as a single facing. */
23327
+ kept: number;
23328
+ /** Detections fused into multi-facing objects. */
23329
+ merged: number;
23330
+ /** Detections that could not be placed. */
23331
+ ignored: number;
23332
+ /** Detections removed by same-frame quality gates. */
23333
+ dropped: number;
23334
+ /** Detections dropped by the physical-size gate. */
23335
+ size_rejected: number;
23336
+ /** Resulting objects (kept + merged clusters). */
23337
+ object_count: number;
23338
+ }
23339
+ /** A detection that could not be placed in world coordinates. */
23340
+ interface IgnoredDetection {
23341
+ _id?: StringId;
23342
+ task?: StringId;
23343
+ annotation_id?: StringId;
23344
+ label_id?: StringId;
23345
+ frame_id?: number;
23346
+ /** `no_pose`, `no_intrinsics`, `no_depth`, `empty_depth_region`, `insufficient_depth_pixels`, `behind_shelf`, or `not_placed`. */
23347
+ reason?: string;
23348
+ }
23349
+ type LedgerDisposition = "new_object" | "re_observation" | "dropped_same_frame" | "size_rejected" | "unplaced";
23350
+ /** Audit trail of ONE detection through the fusion pipeline — where it went and why. */
23351
+ interface DetectionLedgerEntry {
23352
+ _id?: StringId;
23353
+ task?: StringId;
23354
+ annotation_id?: StringId;
23355
+ label_id?: StringId;
23356
+ label_name?: string;
23357
+ frame_id?: number;
23358
+ disposition?: LedgerDisposition;
23359
+ /** Machine cause: `first_observation`, `re_observation`, `duplicate_box_in_frame`, `same_spot_in_frame`, `below_min_confidence`, `implausible_size`, `size_gate`, or an unplaced reason. */
23360
+ reason?: string;
23361
+ /** Human-readable explanation, ready to render. */
23362
+ detail?: string;
23363
+ /** The `objects[]._id` this detection created or merged into. */
23364
+ object_id?: StringId;
23365
+ /** For same-frame drops — the stronger sibling that was kept. */
23366
+ kept_by_annotation_id?: StringId;
23367
+ /** For re-observations — world distance to the object, metres. */
23368
+ matched_distance_m?: number;
23369
+ world?: Vec3;
23370
+ box?: Box2D;
23371
+ confidence?: number;
23372
+ placement_confidence?: number;
23373
+ }
23374
+ /** Fusion funnel counters — the "where did the detections go" summary. */
23375
+ interface AnalysisFunnel {
23376
+ tasks?: number;
23377
+ /** Frames whose boxes came from a confirmed HUMAN annotation group. */
23378
+ tasks_manual?: number;
23379
+ detections_total?: number;
23380
+ unplaced?: number;
23381
+ size_rejected?: number;
23382
+ frames?: number;
23383
+ detections_placed?: number;
23384
+ dropped_same_frame?: number;
23385
+ re_observations?: number;
23386
+ new_objects?: number;
23387
+ /** After the group-consensus merge. */
23388
+ clusters_final?: number;
23389
+ }
23390
+ /** Which annotation group each frame contributed (human truth first). */
23391
+ interface FrameSource {
23392
+ _id?: StringId;
23393
+ task?: StringId;
23394
+ group_id?: StringId;
23395
+ annotation_state?: "auto" | "manual" | "auto_edited";
23396
+ model_version?: StringId;
23397
+ /** Lets the UI flag analyses older than a frame's latest correction. */
23398
+ edit_time?: number;
23399
+ }
23400
+ /** Per-stage config hashes; staleness is a fingerprint diff. */
23401
+ interface StageFingerprints {
23402
+ detect: string;
23403
+ fuse: string;
23404
+ compose: string;
23405
+ }
23406
+ interface ComposedStack {
23407
+ /** 0 = leftmost as the shopper sees the shelf. */
23408
+ index?: number;
23409
+ u_from?: number;
23410
+ u_to?: number;
23411
+ y_from?: number;
23412
+ y_to?: number;
23413
+ /** Plane depth of the stack (m; larger = closer to the shopper). */
23414
+ s?: number;
23415
+ /** 0 = front row (closest to the shopper). */
23416
+ row?: number;
23417
+ /** Refs into `objects[]._id`, ordered bottom-up. */
23418
+ object_ids?: StringId[];
23419
+ }
23420
+ interface ComposedShelf {
23421
+ /** 0 = the lowest shelf. */
23422
+ index?: number;
23423
+ /** Board level (world Y, metres). */
23424
+ y_world?: number;
23425
+ /** Shelf level above the device-locked floor; null when not locked. */
23426
+ height_above_ground_m?: number | null;
23427
+ u_from?: number;
23428
+ u_to?: number;
23429
+ /** Median plane depth of member stacks (metres). */
23430
+ s?: number;
23431
+ stacks?: ComposedStack[];
23432
+ }
23433
+ /** Planogram structure (shelves → stacks → objects) composed from the fused objects' world geometry. */
23434
+ interface ShelfComposition {
23435
+ /** Orthonormal basis: `n` = horizontal unit normal toward the shopper, `u` = up × n (shopper's right). */
23436
+ plane?: {
23437
+ n?: number[];
23438
+ u?: number[];
23439
+ };
23440
+ orientation?: "shopper" | string;
23441
+ shelves?: ComposedShelf[];
23442
+ /** Objects that didn't land on a shelf level (hook walls, sparse levels). */
23443
+ unshelved?: {
23444
+ object_id?: StringId;
23445
+ reason?: string;
23446
+ }[];
23447
+ }
23448
+ /** Bounded shelf plane peeled off the voxel cloud by RANSAC. */
23449
+ interface ShelfPlane {
23450
+ normal?: number[];
23451
+ /** A point on the plane (inlier centroid), metres. */
23452
+ point?: number[];
23453
+ extent_min?: number[];
23454
+ extent_max?: number[];
23455
+ inlier_count?: number;
23456
+ /** Inliers / cloud points, 0..1. */
23457
+ inlier_ratio?: number;
23458
+ }
23459
+ /** Scene-level outputs — all best-effort (omitted when they can't be computed). */
23460
+ interface AnalysisScene {
23461
+ /** Dominant plane over all placed detections (legacy single plane). */
23462
+ plane?: {
23463
+ normal?: number[];
23464
+ d?: number;
23465
+ centroid?: number[];
23466
+ inliers?: number;
23467
+ };
23468
+ /** RANSAC shelf planes, strongest first. */
23469
+ planes?: ShelfPlane[];
23470
+ /** Voxel point cloud metadata; the packed Float32 `[x,y,z,r,g,b,w]` blob lives in `media` (bin). */
23471
+ point_cloud?: {
23472
+ media?: StringId;
23473
+ num_points?: number;
23474
+ voxel_size_m?: number;
23475
+ aabb_min?: number[];
23476
+ aabb_max?: number[];
23477
+ };
23478
+ }
23479
+ interface Data {
23480
+ _id: StringId;
23481
+ company_namespace: string[];
23482
+ disabled: boolean;
23483
+ /** The analyzed `ai-object-detection-session` _id. */
23484
+ session: StringId;
23485
+ /** `ai-object-detection-model-version` _id used to infer not-yet-placed tasks. */
23486
+ model_version?: StringId;
23487
+ config?: SceneMathConfig;
23488
+ status: AnalysisStatus;
23489
+ /** Epoch ms. */
23490
+ started_at?: number;
23491
+ /** Epoch ms. */
23492
+ finished_at?: number;
23493
+ /** Liveness heartbeat (epoch ms) refreshed by the background run; a pending/in_progress run without one for 15 min is auto-failed by the next create. */
23494
+ heartbeat_at?: number;
23495
+ tasks_total: number;
23496
+ /** Tasks inferred during this run (not previously placed). */
23497
+ tasks_inferred: number;
23498
+ /** Detections kept (single facings). */
23499
+ kept_count: number;
23500
+ /** Detections merged into objects. */
23501
+ merged_count: number;
23502
+ /** Detections that could not be placed. */
23503
+ ignored_count: number;
23504
+ /** Detections dropped by the physical-size gate. */
23505
+ size_rejected_count: number;
23506
+ /** Same-frame quality-gate drops. */
23507
+ dropped_count: number;
23508
+ /** Resulting objects (kept + merged). */
23509
+ objects_count: number;
23510
+ objects?: AnalysisObject[];
23511
+ concluded_labels?: ConcludedLabel[];
23512
+ ignored?: IgnoredDetection[];
23513
+ /** Per-detection fusion audit trail (one entry per analyzed annotation). */
23514
+ detections?: DetectionLedgerEntry[];
23515
+ funnel?: AnalysisFunnel;
23516
+ frame_sources?: FrameSource[];
23517
+ stage_fingerprints?: StageFingerprints;
23518
+ /** Present unless `config.shelf_analysis: false` or nothing was placed (null after a compose recompute that produced nothing). */
23519
+ shelf_composition?: ShelfComposition | null;
23520
+ scene?: AnalysisScene;
23521
+ _errors?: any[];
23522
+ /** Who triggered the run (server-stamped). */
23523
+ creator?: AdminOrRep;
23524
+ editor?: AdminOrRep;
23525
+ createdAt: Date;
23526
+ updatedAt: Date;
23527
+ }
23528
+ type PopulatedKeys = "session" | "model_version" | "objects.label_id" | "objects.winning_image_media" | "concluded_labels.label_id";
23529
+ interface AnalysisObjectWithPopulatedKeys extends Omit<AnalysisObject, "label_id" | "winning_image_media"> {
23530
+ label_id?: StringId | AiObjectDetectionLabel.Data;
23531
+ winning_image_media?: StringId | MediaStorage.MediaStorageSchema;
23532
+ }
23533
+ interface ConcludedLabelWithPopulatedKeys extends Omit<ConcludedLabel, "label_id"> {
23534
+ label_id?: StringId | AiObjectDetectionLabel.Data;
23535
+ }
23536
+ /** Every population key is applied IN PLACE (the referenced field is replaced by the populated document). */
23537
+ interface DataWithPopulatedKeys extends Omit<Data, "session" | "model_version" | "objects" | "concluded_labels"> {
23538
+ session: StringId | AiObjectDetectionSession.Data;
23539
+ model_version?: StringId | AiObjectDetectionModelVersion.Data;
23540
+ objects?: AnalysisObjectWithPopulatedKeys[];
23541
+ concluded_labels?: ConcludedLabelWithPopulatedKeys[];
23542
+ }
23543
+ /** `create` is the analysis TRIGGER (async) — or, with `recompute: "compose"`, a synchronous compose-only recompute of an existing analysis. */
23544
+ interface CreateBody {
23545
+ /** The session to analyze. Required unless `recompute` is set. */
23546
+ session?: StringId;
23547
+ /** Model version used to infer not-yet-placed tasks; tasks inferred by a different version are re-inferred. */
23548
+ model_version?: StringId;
23549
+ /** Optional object-detection model _id override (resolves `current_model_version` when `model_version` is absent). */
23550
+ model?: StringId;
23551
+ /** Detector for tasks inferred during this run (default `auto`). */
23552
+ engine?: "auto" | "trained" | "zero_shot";
23553
+ /** Zero-shot VLM override, e.g. `qwen/qwen3-vl-8b-instruct`. */
23554
+ zero_shot_model?: string;
23555
+ /** Detector confidence override. */
23556
+ conf?: number;
23557
+ /** Detector NMS IoU override (0..1). */
23558
+ iou?: number;
23559
+ /** Class-agnostic NMS override. */
23560
+ agnostic_nms?: boolean;
23561
+ config?: SceneMathConfig;
23562
+ /** Re-infer EVERY task even if it already has an auto group for this version (stale-provenance escape hatch). */
23563
+ force_reinference?: boolean;
23564
+ /** Parallel inference calls (1..10, default 4); wins over `config.inference_concurrency`. */
23565
+ inference_concurrency?: number;
23566
+ /** `compose` re-runs ONLY the shelf composition of `analysis` with the `shelf_*` keys of `config`, synchronously and in place. */
23567
+ recompute?: "compose";
23568
+ /** The existing analysis _id to recompute (required with `recompute`). */
23569
+ analysis?: StringId;
23570
+ company_namespace?: string[];
23571
+ }
23572
+ /** PUT applies the body as an update; `editor` is server-stamped. Set `disabled: true` to soft-delete. */
23573
+ type UpdateBody = Partial<Omit<Data, "_id" | "createdAt" | "updatedAt" | "company_namespace" | "creator" | "editor">>;
23574
+ /** Filter keys honoured by `find` and by the bulk `patch`. */
23575
+ type FilterParams = {
23576
+ _id?: StringId | StringId[];
23577
+ session?: StringId | StringId[];
23578
+ model_version?: StringId | StringId[];
23579
+ status?: AnalysisStatus | AnalysisStatus[];
23580
+ "creator._id"?: StringId | StringId[];
23581
+ from_updatedAt?: number;
23582
+ to_updatedAt?: number;
23583
+ from_createdAt?: number;
23584
+ to_createdAt?: number;
23585
+ /** Regex on `name` — analyses carry no `name`, so this matches nothing; listed only because the shared query helper honours it. */
23586
+ search?: string;
23587
+ /** Include disabled (soft-deleted) analyses. */
23588
+ disabled?: boolean;
23589
+ };
23590
+ interface WriteQuery {
23591
+ key: string;
23592
+ command: "set" | "addToSet" | "pull";
23593
+ value: any;
23594
+ }
23595
+ namespace Find {
23596
+ type Params = DefaultPaginationQueryParams & FilterParams & {
23597
+ populatedKeys?: PopulatedKeys[];
23598
+ };
23599
+ interface Result extends DefaultPaginationResult {
23600
+ data: DataWithPopulatedKeys[];
23601
+ }
23602
+ }
23603
+ namespace Get {
23604
+ type ID = StringId;
23605
+ interface Params {
23606
+ populatedKeys?: PopulatedKeys[];
23607
+ }
23608
+ type Result = DataWithPopulatedKeys;
23609
+ }
23610
+ namespace Create {
23611
+ type Body = CreateBody;
23612
+ /** Normal path: the `pending` document plus `message` (work continues in the background — poll until `status` flips). Compose recompute: the updated analysis document (no `message`). */
23613
+ type Result = Data & {
23614
+ message?: string;
23615
+ };
23616
+ }
23617
+ namespace Update {
23618
+ type ID = StringId;
23619
+ type Body = UpdateBody;
23620
+ type Result = Data;
23621
+ }
23622
+ /** Bulk update: `writeQuery[]` is applied to every analysis matching the query filters. */
23623
+ namespace Patch {
23624
+ type Params = FilterParams;
23625
+ type Body = {
23626
+ writeQuery: WriteQuery[];
23627
+ };
23628
+ type Result = {
23629
+ nFound: number;
23630
+ nModified: number;
23631
+ };
23632
+ }
23633
+ namespace Remove {
23634
+ type ID = StringId;
23635
+ type Result = Data;
23636
+ }
23637
+ }
23638
+ namespace AiObjectDetectionSessionElection {
23639
+ type SessionVerdict = AiObjectDetectionSession.SessionVerdict;
23640
+ /** A source frame excluded from the election because it violated the namespace detection-settings ERROR tier. */
23641
+ type ElectionExcluded = AiObjectDetectionSession.ElectionExcluded;
23642
+ /** The NEW session materialized by an election. It is a regular `ai-object-detection-session` document (same collection) whose election fields are always set. */
23643
+ interface Data extends AiObjectDetectionSession.Data {
23644
+ /** The source session's `session_id` suffixed `-E<n>` (n = 1 + prior elections of that source). */
23645
+ session_id: string;
23646
+ /** Provenance — the source session `_id`. */
23647
+ source_session: StringId;
23648
+ /** Average frame quality over ALL frames of the source session, 0..1 (rounded to 3 decimals). */
23649
+ session_score: number;
23650
+ /** `rejected` when any rejection reason fired, else banded from `session_score`. */
23651
+ session_verdict: SessionVerdict;
23652
+ /** `coverage_below_target`, `jump_detected`, `too_many_elected`. */
23653
+ rejection_reasons: string[];
23654
+ /** Error-tier frames dropped from the clone (they stay on the source session). */
23655
+ election_excluded: ElectionExcluded[];
23656
+ }
23657
+ interface CreateBody {
23658
+ /** Source `ai-object-detection-session` _id the frames belong to. */
23659
+ session: StringId;
23660
+ /** Task `_id`s (frames of the source session) elected in the playground — 1..500, all must belong to `session`. */
23661
+ task_ids: StringId[];
23662
+ /** Optional playground formula parameters, stored for provenance only (not persisted on the session model). */
23663
+ config_snapshot?: {
23664
+ [key: string]: number | boolean;
23665
+ };
23666
+ company_namespace?: string[];
23667
+ }
23668
+ namespace Create {
23669
+ type Body = CreateBody;
23670
+ /** The newly created session document (`toObject()` of the insert). */
23671
+ type Result = Data;
23672
+ }
23673
+ }
23674
+ namespace AiObjectDetectionSessionInsight {
23675
+ /** Vocabulary kinds a question target / rollup row can refer to. */
23676
+ type TargetKind = "label" | "label_group" | "brand" | "category" | "subcategory" | "product";
23677
+ type QuestionType = "share_of_shelf" | "blocking" | "adjacency";
23678
+ /** Rollup keys of `share_of_shelf` / `blocking`: `by_label` is always present; the others only when the scene's labels link to entities of that kind. */
23679
+ type RollupKey = "by_label" | "by_label_group" | "by_brand" | "by_category" | "by_subcategory" | "by_product";
23680
+ /** All optional on input; invalid values fall back to the defaults (values are clamped). */
23681
+ interface InsightConfig {
23682
+ /** Echoed for contract compatibility (default 0.06) — shelf levels now come from the analysis's stored composition. */
23683
+ min_shelf_gap_m: number;
23684
+ /** Echoed for contract compatibility (default 0.35). */
23685
+ shelf_gap_height_factor: number;
23686
+ /** Fallback facing width (m) when a facing has no measured size (default 0.08). */
23687
+ default_facing_width_m: number;
23688
+ /** Vertical block merge — required axis overlap (m) between adjacent-shelf runs (default 0.03)… */
23689
+ block_min_overlap_m: number;
23690
+ /** …or this fraction of the narrower run's width, whichever is smaller (default 0.5). */
23691
+ block_overlap_frac: number;
23692
+ }
23693
+ interface InsightQuestion {
23694
+ type: QuestionType;
23695
+ /** Resolved by `id` first, then exact name, then substring, then fuzzy tokens. */
23696
+ target: {
23697
+ kind?: TargetKind;
23698
+ id?: StringId;
23699
+ name?: string;
23700
+ };
23701
+ }
23702
+ interface LabelFacings {
23703
+ label_id: StringId;
23704
+ label_name?: string;
23705
+ facings: number;
23706
+ }
23707
+ interface ShareRow {
23708
+ key: string;
23709
+ kind: TargetKind;
23710
+ name: string;
23711
+ facings: number;
23712
+ linear_cm: number;
23713
+ area_cm2: number;
23714
+ /** 0..1 of all detected facings. */
23715
+ facing_share: number;
23716
+ /** 0..1 of the total linear cm. */
23717
+ linear_share: number;
23718
+ /** 0..1 of the total measured area. */
23719
+ area_share: number;
23720
+ shelves: {
23721
+ shelf_index: number;
23722
+ facings: number;
23723
+ linear_cm: number;
23724
+ linear_share_of_shelf: number;
23725
+ }[];
23726
+ labels: LabelFacings[];
23727
+ }
23728
+ interface BlockRun {
23729
+ shelf_index: number;
23730
+ /** Left edge along the shelf axis û (m). */
23731
+ t0: number;
23732
+ t1: number;
23733
+ facings: number;
23734
+ facing_ids: StringId[];
23735
+ }
23736
+ interface Blocking {
23737
+ present: boolean;
23738
+ facings: number;
23739
+ labels_present: LabelFacings[];
23740
+ blocks_count: number;
23741
+ /** True when every facing of the group sits in ONE contiguous block. */
23742
+ is_single_block: boolean;
23743
+ largest_block_facings: number;
23744
+ /** 0..1. */
23745
+ largest_block_share: number;
23746
+ blocks: {
23747
+ shelves: number[];
23748
+ facings: number;
23749
+ runs: BlockRun[];
23750
+ }[];
23751
+ /** Foreign labels breaking the block, ranked by interrupting facings. */
23752
+ interrupters: LabelFacings[];
23753
+ shelves_spanned: number[];
23754
+ }
23755
+ /** A `blocking` rollup row (`Blocking` + identity + narrative). */
23756
+ interface BlockingRow extends Blocking {
23757
+ key: string;
23758
+ name: string;
23759
+ narrative: string;
23760
+ }
23761
+ interface InsightShelf {
23762
+ /** 0 = bottom shelf. */
23763
+ index: number;
23764
+ /** Board level (m, AR world frame). */
23765
+ y_base_m: number;
23766
+ facings: number;
23767
+ linear_cm: number;
23768
+ span_cm: number;
23769
+ /** linear_cm / span_cm — how much of the used span is product (0..1). */
23770
+ utilization: number;
23771
+ }
23772
+ interface PlanogramFacing {
23773
+ id: StringId;
23774
+ label_id: StringId;
23775
+ label_name?: string;
23776
+ /** 1-based position left→right on the shelf. */
23777
+ position: number;
23778
+ /** Re-based to the shelf's left-most facing (cm). */
23779
+ from_cm: number;
23780
+ to_cm: number;
23781
+ w_cm: number;
23782
+ h_cm?: number;
23783
+ confidence?: number;
23784
+ }
23785
+ interface PlanogramShelf {
23786
+ shelf_index: number;
23787
+ facings: PlanogramFacing[];
23788
+ }
23789
+ interface AdjacencyRow {
23790
+ label_id: StringId;
23791
+ label_name?: string;
23792
+ neighbors: {
23793
+ label_id: StringId;
23794
+ label_name?: string;
23795
+ count: number;
23796
+ }[];
23797
+ }
23798
+ interface AnswerTarget {
23799
+ kind: TargetKind;
23800
+ name: string;
23801
+ label_ids: StringId[];
23802
+ }
23803
+ /** One answer per question, in order. Unresolved answers carry `reason` (+ `suggestions`). */
23804
+ interface Answer {
23805
+ question: string | InsightQuestion;
23806
+ type?: QuestionType;
23807
+ resolved: boolean;
23808
+ reason?: string;
23809
+ suggestions?: string[];
23810
+ target?: AnswerTarget;
23811
+ /** `ShareRow` for share_of_shelf, `Blocking` for blocking, `{ neighbors }` or `{ present: false }` for adjacency. */
23812
+ result?: ShareRow | Blocking | {
23813
+ neighbors: {
23814
+ label_id: StringId;
23815
+ label_name?: string;
23816
+ count: number;
23817
+ }[];
23818
+ } | {
23819
+ present: false;
23820
+ };
23821
+ narrative?: string;
23822
+ }
23823
+ /** The insight payload — computed on read, never persisted. */
23824
+ interface Data {
23825
+ session: StringId;
23826
+ /** The analysis the numbers were derived from (latest successful one unless an explicit `analysis` was given). */
23827
+ analysis: StringId;
23828
+ model_version?: StringId;
23829
+ /** Epoch ms of the analysis run's completion. */
23830
+ analysis_finished_at?: number;
23831
+ /** Epoch ms — always freshly computed. */
23832
+ computed_at: number;
23833
+ config_used: InsightConfig;
23834
+ scene: {
23835
+ facings_placed: number;
23836
+ /** Analysis objects not counted as facings (back rows / unshelved). */
23837
+ objects_skipped: number;
23838
+ shelf_count: number;
23839
+ /** Unit shelf-axis direction û in the horizontal plane. */
23840
+ axis: {
23841
+ dir_x: number;
23842
+ dir_z: number;
23843
+ };
23844
+ };
23845
+ totals: {
23846
+ facings: number;
23847
+ linear_cm: number;
23848
+ area_cm2: number;
23849
+ };
23850
+ shelves: InsightShelf[];
23851
+ share_of_shelf: Partial<Record<RollupKey, ShareRow[]>>;
23852
+ blocking: Partial<Record<RollupKey, BlockingRow[]>>;
23853
+ /** Only with `include_objects`. */
23854
+ objects?: PlanogramShelf[];
23855
+ /** Only with `include_adjacency`. */
23856
+ adjacency?: AdjacencyRow[];
23857
+ /** Only on `create` (POST) when `questions` were sent. */
23858
+ answers?: Answer[];
23859
+ }
23860
+ /** POST body — compute the insight and answer up to 20 questions. One of `session` / `analysis` is required. */
23861
+ interface CreateBody {
23862
+ /** Session _id — reads its LATEST successful analysis. */
23863
+ session?: StringId;
23864
+ /** Explicit analysis _id (must have `status: success`). */
23865
+ analysis?: StringId;
23866
+ /** Free-text strings and/or structured questions, answered in order (max 20). */
23867
+ questions?: (string | InsightQuestion)[];
23868
+ config?: Partial<InsightConfig>;
23869
+ include_objects?: boolean;
23870
+ include_adjacency?: boolean;
23871
+ }
23872
+ namespace Find {
23873
+ /** One of `session` / `analysis` is required. */
23874
+ type Params = {
23875
+ /** Session _id — reads its LATEST successful analysis. */
23876
+ session?: StringId;
23877
+ /** Explicit analysis _id (must have `status: success`). */
23878
+ analysis?: StringId;
23879
+ /** Add the planogram-style facing dump per shelf. */
23880
+ include_objects?: boolean;
23881
+ /** Add label-level neighbour counts. */
23882
+ include_adjacency?: boolean;
23883
+ };
23884
+ /** A single computed payload — NOT paginated. */
23885
+ type Result = Data;
23886
+ }
23887
+ namespace Get {
23888
+ /** The path id is read as a SESSION id (deep link). */
23889
+ type ID = StringId;
23890
+ type Result = Data;
23891
+ }
23892
+ namespace Create {
23893
+ type Body = CreateBody;
23894
+ /** The insight payload plus `answers[]` when questions were sent. */
23895
+ type Result = Data;
23896
+ }
23897
+ }
23898
+ namespace AiObjectDetectionMetric {
23899
+ /** Engine registry key — selects the `args` schema, the output family and the evaluator. */
23900
+ type MetricType = "adjacent_block" | "facings_count" | "on_shelf_availability" | "share_of_shelf";
23901
+ /** Output family: fixes what a result's `answer` means and where its 0..1 `score` comes from. */
23902
+ type MetricOutputFamily = "compatibility" | "numerical" | "share_of_shelf";
23903
+ /** width_cm = linear share (occupied shelf length), area_cm2 = front-face areas, facings = unit count. */
23904
+ type ShareOfShelfMeasure = "width_cm" | "area_cm2" | "facings";
23905
+ /** Args for `type: "adjacent_block"` (compatibility family) — "these labels must stand together". */
23906
+ interface AdjacentBlockArgs {
23907
+ /** Label ids that form the block (≥ 1). */
23908
+ labels: StringId[];
23909
+ /** Minimum member facing count (inclusive, ≥ 0). */
23910
+ from: number;
23911
+ /** Maximum member facing count (inclusive, ≥ from). */
23912
+ to: number;
23913
+ /** Judge only the shopper-visible front row. Default true. */
23914
+ front_row_only?: boolean;
23915
+ }
23916
+ /** Args for `type: "facings_count"` (numerical family) — how many facings of the labels does the shopper see. */
23917
+ interface FacingsCountArgs {
23918
+ /** Label ids to count (≥ 1). */
23919
+ labels: StringId[];
23920
+ /** Optional demanded count (integer ≥ 1) — score = answer ÷ target_answer (clamped to 1). Absent: non-zero answer scores 1, zero scores 0. */
23921
+ target_answer?: number;
23922
+ /** Count only the shopper-visible front row. Default true. */
23923
+ front_row_only?: boolean;
23924
+ }
23925
+ /** Args for `type: "on_shelf_availability"` (numerical family) — how much of what SHOULD be on the shelf is. */
23926
+ interface OnShelfAvailabilityArgs {
23927
+ /** The label ids that SHOULD be on the shelf (≥ 1). */
23928
+ labels: StringId[];
23929
+ /** Optional demanded count of AVAILABLE labels (integer ≥ 1, never above labels.length) — score = answer ÷ target_answer. Absent: score = availability ratio. */
23930
+ target_answer?: number;
23931
+ /** Default FALSE — a product in a back row is still available. */
23932
+ front_row_only?: boolean;
23933
+ }
23934
+ /** One segment row of a share-of-shelf metric. */
23935
+ interface ShareOfShelfSegmentArg {
23936
+ /** Segment id (`/ai-object-detection-segment`) — must exist, not be deleted, and appear in only one row. */
23937
+ segment: StringId;
23938
+ /** Optional per-metric label override; empty/absent ⇒ the segment's own labels. */
23939
+ labels?: StringId[];
23940
+ /** The MAIN row the target_ratio is defined for — exactly one per metric (server flags the first row when none is set). */
23941
+ main?: boolean;
23942
+ }
23943
+ /** Args for `type: "share_of_shelf"` (share_of_shelf family). */
23944
+ interface ShareOfShelfArgs {
23945
+ /** Segment rows (≥ 1); exactly one is `main`. */
23946
+ segments: ShareOfShelfSegmentArg[];
23947
+ /** The share the MAIN segment must reach for full score (0.001..1). */
23948
+ target_ratio: number;
23949
+ /** Default "width_cm". */
23950
+ measure?: ShareOfShelfMeasure;
23951
+ /** Measure only the shopper-visible front row. Default true. */
23952
+ front_row_only?: boolean;
23953
+ /** width_cm only (default true): count each vertical pile ONCE (the bottom object books the shelf distance). false = legacy per-unit width sum. */
23954
+ first_in_stack?: boolean;
23955
+ }
23956
+ /** Client-sent args — must match the metric's `type`. Unknown keys are dropped and defaults applied server-side. */
23957
+ type MetricArgs = AdjacentBlockArgs | FacingsCountArgs | OnShelfAvailabilityArgs | ShareOfShelfArgs;
23958
+ /** Stored args: the client shape plus the server-stamped discriminator mirror `type` (clients never send it). */
23959
+ type StoredMetricArgs = MetricArgs & {
23960
+ type?: MetricType;
23961
+ };
23962
+ /** One argument declaration from the type registry (`find({ registry: true })`). */
23963
+ interface MetricArgField {
23964
+ key: string;
23965
+ type: "labels" | "number" | "integer" | "boolean" | "enum" | "segments";
23966
+ required: boolean;
23967
+ /** Numeric bounds (number/integer). */
23968
+ min?: number;
23969
+ max?: number;
23970
+ /** Default applied when an optional arg is absent. */
23971
+ default?: unknown;
23972
+ /** labels / segments: minimum item count. */
23973
+ min_items?: number;
23974
+ /** enum: the allowed values. */
23975
+ options?: string[];
23976
+ }
23977
+ /** One metric TYPE declaration from the registry — lets generic clients render forms without hardcoding. */
23978
+ interface MetricTypeDeclaration {
23979
+ type: MetricType;
23980
+ output: MetricOutputFamily;
23981
+ args: MetricArgField[];
23982
+ /** Result output keys a human may override (`score`/`ratio` are always derived). */
23983
+ overwritable: string[];
23984
+ }
23985
+ interface Data {
23986
+ _id: StringId;
23987
+ name: string;
23988
+ description?: string;
23989
+ type: MetricType;
23990
+ args: StoredMetricArgs;
23991
+ /** false pauses evaluation without losing the definition. Default true. */
23992
+ enabled: boolean;
23993
+ /** Soft-delete flag. */
23994
+ disabled: boolean;
23995
+ /** Server-stamped from the creating token. */
23996
+ creator?: AdminOrRepOrTenantOrClient;
23997
+ /** Server-stamped on update / remove. */
23998
+ editor?: AdminOrRepOrTenantOrClient;
23999
+ company_namespace: string[];
24000
+ createdAt: Date;
24001
+ updatedAt: Date;
24002
+ }
24003
+ interface CreateBody {
24004
+ name: string;
24005
+ description?: string;
24006
+ type: MetricType;
24007
+ /** Validated against `type`'s schema; EVERY violation is reported in one 400. share_of_shelf rows must reference live segments. */
24008
+ args: MetricArgs;
24009
+ enabled?: boolean;
24010
+ company_namespace?: string[];
24011
+ }
24012
+ /** PUT re-validates `type` + `args` (both required — the update is a full re-definition); `_id`, `company_namespace` and `creator` are stripped server-side. */
24013
+ interface UpdateBody {
24014
+ type: MetricType;
24015
+ args: MetricArgs;
24016
+ name?: string;
24017
+ description?: string;
24018
+ enabled?: boolean;
24019
+ /** Set true to soft-delete via update. */
24020
+ disabled?: boolean;
24021
+ }
24022
+ namespace Find {
24023
+ type Params = DefaultPaginationQueryParams & {
24024
+ _id?: StringId | StringId[];
24025
+ /** Exact name match. */
24026
+ name?: string | string[];
24027
+ type?: MetricType | MetricType[];
24028
+ enabled?: boolean;
24029
+ /** Case-insensitive regex on `name`. */
24030
+ search?: string;
24031
+ disabled?: boolean;
24032
+ from_updatedAt?: number;
24033
+ to_updatedAt?: number;
24034
+ from_createdAt?: number;
24035
+ to_createdAt?: number;
24036
+ /** true ⇒ the response is a `RegistryResult` (type declarations) instead of a paginated list. */
24037
+ registry?: boolean;
24038
+ };
24039
+ interface PaginatedResult extends DefaultPaginationResult {
24040
+ data: Data[];
24041
+ }
24042
+ /** Returned when `registry: true` is passed. */
24043
+ interface RegistryResult {
24044
+ /** Bumped whenever a type's semantics change; results carry the version they were computed with. */
24045
+ engine_version: number;
24046
+ types: MetricTypeDeclaration[];
24047
+ }
24048
+ type Result = PaginatedResult | RegistryResult;
24049
+ }
24050
+ namespace Get {
24051
+ type ID = StringId;
24052
+ type Result = Data;
24053
+ }
24054
+ namespace Create {
24055
+ type Body = CreateBody;
24056
+ type Result = Data;
24057
+ }
24058
+ namespace Update {
24059
+ type ID = StringId;
24060
+ type Body = UpdateBody;
24061
+ type Result = Data;
24062
+ }
24063
+ namespace Remove {
24064
+ type ID = StringId;
24065
+ /** The soft-deleted document (`disabled: true`). */
24066
+ type Result = Data;
24067
+ }
24068
+ }
24069
+ namespace AiObjectDetectionMetricResult {
24070
+ /** Evaluation outcome of one result row. */
24071
+ type Status = "ok" | "error";
24072
+ /** Computed layer for `type: "adjacent_block"` (compatibility family). */
24073
+ interface AdjacentBlockComputed {
24074
+ /** Internal discriminator mirror of the result's `type`. */
24075
+ type?: "adjacent_block";
24076
+ output: "compatibility";
24077
+ /** The verdict — contiguous AND count within [from, to]. */
24078
+ answer: boolean;
24079
+ score: number;
24080
+ member_count: number;
24081
+ cut_count: number;
24082
+ in_range: boolean;
24083
+ /** Contiguous member runs (1-based shelf / stack indices). */
24084
+ blocks: {
24085
+ shelf: number;
24086
+ from_stack: number;
24087
+ to_stack: number;
24088
+ count: number;
24089
+ }[];
24090
+ }
24091
+ /** Computed layer for `type: "facings_count"` (numerical family). */
24092
+ interface FacingsCountComputed {
24093
+ type?: "facings_count";
24094
+ output: "numerical";
24095
+ /** The collected facings count. */
24096
+ answer: number;
24097
+ /** Echo of the metric's optional target — present only when set; score then = answer ÷ target_answer (clamped). */
24098
+ target_answer?: number;
24099
+ score: number;
24100
+ shelves: {
24101
+ shelf: number;
24102
+ count: number;
24103
+ }[];
24104
+ }
24105
+ /** Computed layer for `type: "on_shelf_availability"` (numerical family). */
24106
+ interface OnShelfAvailabilityComputed {
24107
+ type?: "on_shelf_availability";
24108
+ output: "numerical";
24109
+ /** How many of the selected labels are AVAILABLE (≥ 1 facing). */
24110
+ answer: number;
24111
+ /** answer ÷ target_answer when a target is set, else `ratio`. */
24112
+ score: number;
24113
+ /** How many labels were selected (the denominator). */
24114
+ total: number;
24115
+ /** answer / total — availability. */
24116
+ ratio: number;
24117
+ target_answer?: number;
24118
+ /** The out-of-stock labels (names resolved at evaluation time). */
24119
+ missing: {
24120
+ label: StringId;
24121
+ name?: string;
24122
+ }[];
24123
+ /** The available labels with their facing counts. */
24124
+ present: {
24125
+ label: StringId;
24126
+ name?: string;
24127
+ facings: number;
24128
+ }[];
24129
+ }
24130
+ /** One segment row's outcome inside a share-of-shelf evaluation; only the MAIN row carries target/score. */
24131
+ interface SegmentOutput {
24132
+ segment: StringId;
24133
+ name: string;
24134
+ /** false = the segment was deleted after the metric referenced it. */
24135
+ resolved: boolean;
24136
+ /** The row the metric's target is defined for — exactly one per metric. */
24137
+ main: boolean;
24138
+ /** Effective labels used (override or the segment's own). */
24139
+ labels: StringId[];
24140
+ /** Measured quantity in the metric's measure unit. */
24141
+ answer: number;
24142
+ /** CONFIRMED human answer override colocated in the row (effective = overwrite_answer ?? answer); present only while `confirmed_edit`. */
24143
+ overwrite_answer?: number;
24144
+ overwrite_ratio?: number;
24145
+ overwrite_score?: number;
24146
+ /** answer / category total. */
24147
+ ratio: number;
24148
+ /** Main row only. */
24149
+ target_ratio?: number;
24150
+ /** Main row only — target_ratio × total. */
24151
+ target_answer?: number;
24152
+ /** Main row only — min(1, ratio / target_ratio). */
24153
+ score?: number;
24154
+ }
24155
+ /** Computed layer for `type: "share_of_shelf"` — the MAIN segment's numbers at the top level, every row in `segments[]`. */
24156
+ interface ShareOfShelfComputed {
24157
+ type?: "share_of_shelf";
24158
+ output: "share_of_shelf";
24159
+ /** The MAIN segment's measured quantity; null when nothing was measurable. */
24160
+ answer: number | null;
24161
+ /** = the main segment's score. */
24162
+ score: number;
24163
+ /** The considered category: the same measure over facings of ANY of the metric's segments. */
24164
+ total: number;
24165
+ /** answer / total. */
24166
+ ratio: number;
24167
+ target_ratio: number;
24168
+ /** target_ratio × total. */
24169
+ target_answer: number;
24170
+ measure: AiObjectDetectionMetric.ShareOfShelfMeasure;
24171
+ /** Considered facings without a physical size (excluded from both sides). */
24172
+ unmeasured: number;
24173
+ segments: SegmentOutput[];
24174
+ }
24175
+ /** Machine layer — strict per-type engine output. Never editable through the API. */
24176
+ type Computed = AdjacentBlockComputed | FacingsCountComputed | OnShelfAvailabilityComputed | ShareOfShelfComputed;
24177
+ /** Stored human layer for `adjacent_block` (entered values + server-derived `score`). */
24178
+ interface AdjacentBlockOverwrite {
24179
+ type?: "adjacent_block";
24180
+ answer?: boolean;
24181
+ score?: number;
24182
+ member_count?: number;
24183
+ cut_count?: number;
24184
+ }
24185
+ interface FacingsCountOverwrite {
24186
+ type?: "facings_count";
24187
+ answer?: number;
24188
+ score?: number;
24189
+ }
24190
+ interface OnShelfAvailabilityOverwrite {
24191
+ type?: "on_shelf_availability";
24192
+ answer?: number;
24193
+ ratio?: number;
24194
+ score?: number;
24195
+ }
24196
+ interface ShareOfShelfOverwrite {
24197
+ type?: "share_of_shelf";
24198
+ answer?: number;
24199
+ total?: number;
24200
+ ratio?: number;
24201
+ score?: number;
24202
+ }
24203
+ /** Stored human layer — sparse VALUE overrides plus the server-DERIVED `ratio`/`score`. Survives recalculation. */
24204
+ type Overwrite = AdjacentBlockOverwrite | FacingsCountOverwrite | OnShelfAvailabilityOverwrite | ShareOfShelfOverwrite;
24205
+ /** What a client may ENTER as an override: only the type's overwritable VALUE keys. `ratio`/`score` are rejected (derived server-side). Send `{}` to clear. */
24206
+ interface OverwriteInput {
24207
+ /** adjacent_block: boolean verdict; numerical / share_of_shelf types: number. */
24208
+ answer?: boolean | number;
24209
+ /** adjacent_block only. */
24210
+ member_count?: number;
24211
+ /** adjacent_block only. */
24212
+ cut_count?: number;
24213
+ /** share_of_shelf only. */
24214
+ total?: number;
24215
+ }
24216
+ interface Data {
24217
+ _id: StringId;
24218
+ /** The metric definition this result was computed from. */
24219
+ metric: StringId;
24220
+ /** The session analysis it was computed against. */
24221
+ analysis: StringId;
24222
+ session?: StringId;
24223
+ /** Denormalized scan context (stamped by the evaluator): the SCANNING rep — unset for admin-scanned sessions. */
24224
+ user?: StringId | null;
24225
+ user_name?: string | null;
24226
+ client?: StringId | null;
24227
+ client_name?: string | null;
24228
+ /** The scanning rep's team ids at evaluation time. */
24229
+ teams?: StringId[];
24230
+ /** Device visit id the scan happened in (copied from the session). */
24231
+ visit_id?: string;
24232
+ route?: StringId;
24233
+ /** Business day of the scan, `YYYY-MM-DD`. */
24234
+ business_day?: string;
24235
+ /** Metric name snapshot at evaluation time. */
24236
+ name?: string;
24237
+ type: AiObjectDetectionMetric.MetricType;
24238
+ /** The type's output family (denormalized for rendering). */
24239
+ output?: AiObjectDetectionMetric.MetricOutputFamily;
24240
+ /** Metric args snapshot at evaluation time. */
24241
+ args?: AiObjectDetectionMetric.StoredMetricArgs;
24242
+ /** Machine layer; absent when the evaluation errored. */
24243
+ computed?: Computed;
24244
+ /** Human layer (default `{ type }`). */
24245
+ overwrite?: Overwrite;
24246
+ /** Human attention marker. Default false. */
24247
+ flag: boolean;
24248
+ /** Admin review gate — the overwrite only drives the effective values while true. Default false. */
24249
+ confirmed_edit: boolean;
24250
+ /** EFFECTIVE score 0..1 (confirmed `overwrite.score`, else `computed.score`). */
24251
+ score?: number;
24252
+ /** EFFECTIVE answer (boolean verdict or number per family). */
24253
+ answer?: boolean | number | null;
24254
+ /** EFFECTIVE ratio (OSA availability / SOS share); null for types without one. */
24255
+ ratio?: number | null;
24256
+ status: Status;
24257
+ error?: string | null;
24258
+ /** Unix ms of the evaluation. */
24259
+ evaluated_at?: number;
24260
+ engine_version?: number;
24261
+ /** The analysis's stage fingerprints at evaluation time — mismatch vs the analysis's current ones = stale. */
24262
+ source_fingerprints?: {
24263
+ [key: string]: any;
24264
+ };
24265
+ creator?: AdminOrRepOrTenantOrClient;
24266
+ editor?: AdminOrRepOrTenantOrClient;
24267
+ disabled: boolean;
24268
+ company_namespace: string[];
24269
+ createdAt: Date;
24270
+ updatedAt: Date;
24271
+ }
24272
+ type PopulatedKeys = "metric" | "analysis" | "session";
24273
+ /** Populated refs replace the id IN PLACE (the backend's population map has no `new_key`). */
24274
+ type DataWithPopulatedKeys = Omit<Data, "metric" | "analysis" | "session"> & {
24275
+ metric: StringId | AiObjectDetectionMetric.Data;
24276
+ analysis: StringId | AiObjectDetectionSessionAnalysis.Data;
24277
+ session?: StringId | AiObjectDetectionSession.Data;
24278
+ };
24279
+ /** POST = CALCULATE: evaluates the mission-assigned metrics of `analysis` (admin only). */
24280
+ interface CreateBody {
24281
+ /** The SUCCESSFUL analysis to evaluate. */
24282
+ analysis: StringId;
24283
+ /** Optional metric-id subset — can only NARROW the mission-assigned set, never widen it. */
24284
+ metrics?: StringId[];
24285
+ }
24286
+ /** PUT = HUMAN OVERRIDE. Only these keys are writable; `computed` is immutable. */
24287
+ interface UpdateBody {
24288
+ /** Reps may only RAISE it (clearing is an admin review action). */
24289
+ flag?: boolean;
24290
+ /** Sparse value overrides; a rep's override auto-raises `flag`. Any fresh overwrite resets `confirmed_edit` to false unless the same admin request confirms it. */
24291
+ overwrite?: OverwriteInput;
24292
+ /** ADMIN-ONLY review verdict — true makes the standing overwrite effective. */
24293
+ confirmed_edit?: boolean;
24294
+ }
24295
+ namespace Find {
24296
+ type Params = DefaultPaginationQueryParams & {
24297
+ _id?: StringId | StringId[];
24298
+ metric?: StringId | StringId[];
24299
+ analysis?: StringId | StringId[];
24300
+ session?: StringId | StringId[];
24301
+ /** The scanning rep's id. */
24302
+ user?: StringId | StringId[];
24303
+ client?: StringId | StringId[];
24304
+ teams?: StringId | StringId[];
24305
+ type?: AiObjectDetectionMetric.MetricType | AiObjectDetectionMetric.MetricType[];
24306
+ flag?: boolean;
24307
+ status?: Status | Status[];
24308
+ /** Regex on the metric-name snapshot. */
24309
+ search?: string;
24310
+ disabled?: boolean;
24311
+ from_updatedAt?: number;
24312
+ to_updatedAt?: number;
24313
+ from_createdAt?: number;
24314
+ to_createdAt?: number;
24315
+ populatedKeys?: PopulatedKeys[];
24316
+ };
24317
+ interface Result extends DefaultPaginationResult {
24318
+ data: DataWithPopulatedKeys[];
24319
+ }
24320
+ }
24321
+ namespace Get {
24322
+ type ID = StringId;
24323
+ type Result = Data;
24324
+ }
24325
+ namespace Create {
24326
+ type Body = CreateBody;
24327
+ /** Evaluation summary + the fresh result documents of the analysis. */
24328
+ interface Result {
24329
+ analysis: string;
24330
+ /** Number of metrics evaluated. */
24331
+ evaluated: number;
24332
+ /** Number of metrics whose evaluation or persistence failed. */
24333
+ errors: number;
24334
+ results: Data[];
24335
+ }
24336
+ }
24337
+ namespace Update {
24338
+ type ID = StringId;
24339
+ type Body = UpdateBody;
24340
+ type Result = Data;
24341
+ }
24342
+ namespace Remove {
24343
+ type ID = StringId;
24344
+ /** The soft-deleted document (admin only). */
24345
+ type Result = Data;
24346
+ }
24347
+ }
24348
+ namespace AiObjectDetectionMission {
24349
+ /** What an assignment rule DEMANDS of a mission at a client (rolled up to the strictest on read). */
24350
+ type RequirementMode = "not_required" | "submission_required" | "completion_required";
24351
+ /** One weighted metric row — the mission's composition. */
24352
+ interface MetricRowInput {
24353
+ metric: StringId;
24354
+ /** Finite number ≥ 0; the mission score is the weighted mean of the metrics' effective result scores. */
24355
+ weight: number;
24356
+ }
24357
+ /** Stored row (Mongoose adds a sub-document `_id`). */
24358
+ interface MetricRow extends MetricRowInput {
24359
+ _id?: StringId;
24360
+ }
24361
+ interface Data {
24362
+ _id: StringId;
24363
+ name: string;
24364
+ description?: string;
24365
+ metrics: MetricRow[];
24366
+ /** Completion threshold 0..1 (default 0 = any NON-ZERO score completes; a zero score never does). */
24367
+ min_score: number;
24368
+ /** Optional detection category a session started FROM this mission carries; null = none. */
24369
+ category?: StringId | null;
24370
+ /** Disabled missions are skipped by evaluation and `scores_for`. Default true. */
24371
+ enabled: boolean;
24372
+ disabled: boolean;
24373
+ creator?: AdminOrRepOrTenantOrClient;
24374
+ editor?: AdminOrRepOrTenantOrClient;
24375
+ company_namespace: string[];
24376
+ createdAt: Date;
24377
+ updatedAt: Date;
24378
+ }
24379
+ interface CreateBody {
24380
+ name: string;
24381
+ description?: string;
24382
+ /** REQUIRED, ≥ 1 row; every violation is listed in one 400. */
24383
+ metrics: MetricRowInput[];
24384
+ /** 0..1; absent/null/"" = default 0. */
24385
+ min_score?: number | null;
24386
+ /** Valid category id, or null/"" to leave unset. */
24387
+ category?: StringId | null;
24388
+ enabled?: boolean;
24389
+ company_namespace?: string[];
24390
+ }
24391
+ /** PUT re-runs the same validation as create — `metrics` (≥ 1 row) is required again. `category: null` explicitly CLEARS it; omitting it leaves the stored value. */
24392
+ interface UpdateBody {
24393
+ metrics: MetricRowInput[];
24394
+ name?: string;
24395
+ description?: string;
24396
+ min_score?: number | null;
24397
+ category?: StringId | null;
24398
+ enabled?: boolean;
24399
+ /** Set true to soft-delete via update. */
24400
+ disabled?: boolean;
24401
+ }
24402
+ /** One metric's contribution inside a `scores_for` row. */
24403
+ interface ScoreMetricRow {
24404
+ metric: string;
24405
+ /** Name snapshot from the metric result (absent when missing). */
24406
+ name?: string;
24407
+ weight: number;
24408
+ /** Effective 0..1 score of the metric's result; absent when `missing`. */
24409
+ score?: number;
24410
+ /** true = the metric has no result on this analysis (counts as 0 in the weighted mean). */
24411
+ missing: boolean;
24412
+ }
24413
+ /** One enabled mission scored against an analysis (`find({ scores_for })`). */
24414
+ interface MissionScoreRow {
24415
+ _id: string;
24416
+ name: string;
24417
+ /** An enabled assignment rule attaches a set containing this mission to the session's client. */
24418
+ assigned: boolean;
24419
+ /** Names of the assigned sets this mission arrived through. */
24420
+ via_sets: string[];
24421
+ /** Strictest demand the matched rules place on it; absent when unassigned. */
24422
+ requirement_mode?: RequirementMode;
24423
+ /** Weighted mean 0..1 of the metrics' effective scores. */
24424
+ score: number;
24425
+ min_score: number;
24426
+ /** NON-ZERO score ≥ min_score — computed at read, never stamped. */
24427
+ completed: boolean;
24428
+ metrics: ScoreMetricRow[];
24429
+ }
24430
+ namespace Find {
24431
+ type Params = DefaultPaginationQueryParams & {
24432
+ _id?: StringId | StringId[];
24433
+ name?: string | string[];
24434
+ enabled?: boolean;
24435
+ /** Case-insensitive regex on `name`. */
24436
+ search?: string;
24437
+ disabled?: boolean;
24438
+ from_updatedAt?: number;
24439
+ to_updatedAt?: number;
24440
+ from_createdAt?: number;
24441
+ to_createdAt?: number;
24442
+ /** An analysis `_id` — the response becomes `ScoresResult` (resolved mission scores for that analysis) instead of a paginated list. */
24443
+ scores_for?: StringId;
24444
+ };
24445
+ interface PaginatedResult extends DefaultPaginationResult {
24446
+ data: Data[];
24447
+ }
24448
+ /** Returned when `scores_for` is passed; assigned missions first, then by name. */
24449
+ interface ScoresResult {
24450
+ analysis: string;
24451
+ /** The analysis's session client; null when the session has none. */
24452
+ client: string | null;
24453
+ missions: MissionScoreRow[];
24454
+ }
24455
+ type Result = PaginatedResult | ScoresResult;
24456
+ }
24457
+ namespace Get {
24458
+ type ID = StringId;
24459
+ type Result = Data;
24460
+ }
24461
+ namespace Create {
24462
+ type Body = CreateBody;
24463
+ type Result = Data;
24464
+ }
24465
+ namespace Update {
24466
+ type ID = StringId;
24467
+ type Body = UpdateBody;
24468
+ type Result = Data;
24469
+ }
24470
+ namespace Remove {
24471
+ type ID = StringId;
24472
+ /** The soft-deleted document (`disabled: true`). */
24473
+ type Result = Data;
24474
+ }
24475
+ }
24476
+ namespace AiObjectDetectionMissionSet {
24477
+ interface Data {
24478
+ _id: StringId;
24479
+ name: string;
24480
+ description?: string;
24481
+ /** Mission ids in this set — the unit assignment rules target. */
24482
+ missions: StringId[];
24483
+ /** Disabled sets are ignored by assignment resolution. Default true. */
24484
+ enabled: boolean;
24485
+ disabled: boolean;
24486
+ creator?: AdminOrRepOrTenantOrClient;
24487
+ editor?: AdminOrRepOrTenantOrClient;
24488
+ company_namespace: string[];
24489
+ createdAt: Date;
24490
+ updatedAt: Date;
24491
+ }
24492
+ interface CreateBody {
24493
+ name: string;
24494
+ description?: string;
24495
+ /** REQUIRED, ≥ 1 valid mission id; every violation is listed in one 400. */
24496
+ missions: StringId[];
24497
+ enabled?: boolean;
24498
+ company_namespace?: string[];
24499
+ }
24500
+ /** PUT re-runs the same validation as create — `missions` (≥ 1 id) is required again. */
24501
+ interface UpdateBody {
24502
+ missions: StringId[];
24503
+ name?: string;
24504
+ description?: string;
24505
+ enabled?: boolean;
24506
+ /** Set true to soft-delete via update. */
24507
+ disabled?: boolean;
24508
+ }
24509
+ namespace Find {
24510
+ type Params = DefaultPaginationQueryParams & {
24511
+ _id?: StringId | StringId[];
24512
+ name?: string | string[];
24513
+ enabled?: boolean;
24514
+ /** Case-insensitive regex on `name`. */
24515
+ search?: string;
24516
+ disabled?: boolean;
24517
+ from_updatedAt?: number;
24518
+ to_updatedAt?: number;
24519
+ from_createdAt?: number;
24520
+ to_createdAt?: number;
24521
+ };
24522
+ interface Result extends DefaultPaginationResult {
24523
+ data: Data[];
24524
+ }
24525
+ }
24526
+ namespace Get {
24527
+ type ID = StringId;
24528
+ type Result = Data;
24529
+ }
24530
+ namespace Create {
24531
+ type Body = CreateBody;
24532
+ type Result = Data;
24533
+ }
24534
+ namespace Update {
24535
+ type ID = StringId;
24536
+ type Body = UpdateBody;
24537
+ type Result = Data;
24538
+ }
24539
+ namespace Remove {
24540
+ type ID = StringId;
24541
+ /** The soft-deleted document (`disabled: true`). */
24542
+ type Result = Data;
24543
+ }
24544
+ }
24545
+ namespace AiObjectDetectionMissionResults {
24546
+ /** The STORED outcome of one mission execution — exactly one document per (session, mission), written by the metric evaluator. */
24547
+ interface Data {
24548
+ _id: StringId;
24549
+ /** The identity unit: one scan = one execution. */
24550
+ session: StringId;
24551
+ mission: StringId;
24552
+ /** The analysis this result CURRENTLY reflects — re-analysis re-points it (no duplicate rows). */
24553
+ analysis: StringId;
24554
+ /** Denormalized scan context: the SCANNING rep (unset for admin-scanned sessions). */
24555
+ user?: StringId | null;
24556
+ user_name?: string | null;
24557
+ client?: StringId | null;
24558
+ client_name?: string | null;
24559
+ teams?: StringId[];
24560
+ /** Device visit id the scan happened in (copied from the session; absent without a visit). */
24561
+ visit_id?: string;
24562
+ route?: StringId;
24563
+ /** Business day of the scan, `YYYY-MM-DD`. */
24564
+ business_day?: string;
24565
+ /** Analysis time (Unix ms) ≈ the visit. */
24566
+ time?: number;
24567
+ /** The session was STARTED FROM this mission ("SCAN THIS MISSION"). Default false. */
24568
+ scanned: boolean;
24569
+ mission_name?: string;
24570
+ /** Weighted EFFECTIVE metric scores 0..1 (confirmed human overrides folded in; a demanded metric without a result counts 0). */
24571
+ score?: number;
24572
+ /** Mission threshold snapshot (default 0). */
24573
+ min_score: number;
24574
+ /** score reached min_score — a ZERO score never completes. */
24575
+ completed: boolean;
24576
+ /** Mission-set names that assigned it (snapshot at evaluation time; [] when unresolved). */
24577
+ via_sets: string[];
24578
+ /** DERIVED — any of the mission's metric results on `analysis` carries a flag. A fresh flag reopens `resolved`. */
24579
+ flagged: boolean;
24580
+ /** Admin review verdict — the only client-writable field. */
24581
+ resolved: boolean;
24582
+ /** Stamped when resolved; null when un-resolved. */
24583
+ resolver?: AdminOrRepOrTenantOrClient | null;
24584
+ /** Unix ms; null when un-resolved. */
24585
+ resolved_at?: number | null;
24586
+ creator?: AdminOrRepOrTenantOrClient;
24587
+ editor?: AdminOrRepOrTenantOrClient;
24588
+ disabled: boolean;
24589
+ company_namespace: string[];
24590
+ createdAt: Date;
24591
+ updatedAt: Date;
24592
+ }
24593
+ /** PUT accepts ONLY the admin review verdict. */
24594
+ interface UpdateBody {
24595
+ /** true stamps `resolver` + `resolved_at`; false clears both. */
24596
+ resolved: boolean;
24597
+ }
24598
+ namespace Find {
24599
+ type Params = DefaultPaginationQueryParams & {
24600
+ _id?: StringId | StringId[];
24601
+ session?: StringId | StringId[];
24602
+ mission?: StringId | StringId[];
24603
+ analysis?: StringId | StringId[];
24604
+ client?: StringId | StringId[];
24605
+ /** The scanning rep's id (overridden by `rep`; a rep token is always forced to itself). */
24606
+ user?: StringId | StringId[];
24607
+ teams?: StringId | StringId[];
24608
+ /** Device `visits.visit_id`. */
24609
+ visit_id?: string | string[];
24610
+ route?: StringId | StringId[];
24611
+ scanned?: boolean;
24612
+ completed?: boolean;
24613
+ flagged?: boolean;
24614
+ resolved?: boolean;
24615
+ /** Window start on `time` (Unix ms). Default = 30 days before `to_time`. */
24616
+ from_time?: number;
24617
+ /** Window end on `time` (Unix ms). Default = now. */
24618
+ to_time?: number;
24619
+ from_createdAt?: number;
24620
+ to_createdAt?: number;
24621
+ /** Admin only: narrow to one rep (mapped to `user`); ignored unless a valid ObjectId. */
24622
+ rep?: StringId;
24623
+ /** Regex on `name` — this model has no `name`, so it matches nothing; listed only because the shared query layer accepts it. */
24624
+ search?: string;
24625
+ disabled?: boolean;
24626
+ };
24627
+ /** Sorted `time` desc, `_id` desc (the `sort` param is ignored). */
24628
+ interface Result extends DefaultPaginationResult {
24629
+ data: Data[];
24630
+ }
24631
+ }
24632
+ namespace Get {
24633
+ type ID = StringId;
24634
+ interface Params {
24635
+ /** Admin only: the document must belong to this rep (`user`). */
24636
+ rep?: StringId;
24637
+ }
24638
+ type Result = Data;
24639
+ }
24640
+ namespace Update {
24641
+ type ID = StringId;
24642
+ type Body = UpdateBody;
24643
+ type Result = Data;
24644
+ }
24645
+ namespace Remove {
24646
+ type ID = StringId;
24647
+ /** The soft-deleted document (admin only). */
24648
+ type Result = Data;
24649
+ }
24650
+ }
24651
+ namespace AiObjectDetectionAssignmentRule {
24652
+ /** Client attribute a rule line is checked against. `client_tag` and `area_tag` both match the client's single `tags` array. */
24653
+ type RuleConditionKey = "client" | "client_tag" | "client_channel" | "assigned_to" | "chain" | "area_tag" | "team";
24654
+ /** `in` = any overlap with the client's value(s); `nin` = no overlap (a client missing the attribute passes `nin`, fails `in`). */
24655
+ type RuleConditionOperator = "in" | "nin";
24656
+ /** How often the rule DEMANDS its sets: due on every visit, or `times` completions per business day / week / month / quarter. */
24657
+ type RuleFrequencyInterval = "every_visit" | "day" | "week" | "month" | "quarter";
24658
+ /** What the rule demands of a set's missions at the client. Carried by the backend, enforced by the mobile app when the rep ends the visit. */
24659
+ type RequirementMode = "not_required" | "submission_required" | "completion_required";
24660
+ interface RuleCondition {
24661
+ /** Mongoose subdocument id (server-generated on stored rows). */
24662
+ _id?: StringId;
24663
+ key: RuleConditionKey;
24664
+ operator: RuleConditionOperator;
24665
+ /** Ids matching the key: clients / client-type tags / channels / reps / chain clients (isChain) / area-type tags / teams. At least one. */
24666
+ value: StringId[];
24667
+ }
24668
+ interface RuleFrequency {
24669
+ interval: RuleFrequencyInterval;
24670
+ /** Completions required per interval (integer >= 1). Pinned to 1 for `every_visit`. */
24671
+ times: number;
24672
+ }
24673
+ /** One stored `mission_sets` line. */
24674
+ interface RuleMissionSet {
24675
+ mission_set: StringId;
24676
+ requirement_mode: RequirementMode;
24677
+ }
24678
+ /** One `mission_sets` line as sent on write — `requirement_mode` defaults to `submission_required`. */
24679
+ interface RuleMissionSetInput {
24680
+ mission_set: StringId;
24681
+ requirement_mode?: RequirementMode;
24682
+ }
24683
+ /** Frequency as sent on write — absent => `every_visit`; `times` absent => 1. */
24684
+ interface RuleFrequencyInput {
24685
+ interval?: RuleFrequencyInterval;
24686
+ times?: number;
24687
+ }
24688
+ interface Data {
24689
+ _id: StringId;
24690
+ name: string;
24691
+ /** The mission sets this rule assigns, one line per set (a set appears once). Never empty. */
24692
+ mission_sets: RuleMissionSet[];
24693
+ /** AND-ed lines; empty = applies to every client. */
24694
+ conditions: RuleCondition[];
24695
+ /** Always present on stored rows (normalized on write). */
24696
+ frequency: RuleFrequency;
24697
+ /** Default true. Disabled-but-enabled rules are still skipped by the assignment read. */
24698
+ enabled: boolean;
24699
+ disabled: boolean;
24700
+ /** Server-stamped from the caller's token on create. */
24701
+ creator?: AdminOrRepOrTenantOrClient;
24702
+ /** Server-stamped from the caller's token on update / remove. */
24703
+ editor?: AdminOrRepOrTenantOrClient;
24704
+ company_namespace: string[];
24705
+ createdAt: Date;
24706
+ updatedAt: Date;
24707
+ }
24708
+ interface CreateBody {
24709
+ name: string;
24710
+ /** REQUIRED, at least one line; each set may be listed once. */
24711
+ mission_sets: RuleMissionSetInput[];
24712
+ /** Omit or send `[]` to apply the rule to every client. Every violation is reported in one 400. */
24713
+ conditions?: RuleCondition[];
24714
+ frequency?: RuleFrequencyInput;
24715
+ /** Default true. */
24716
+ enabled?: boolean;
24717
+ company_namespace?: string[];
24718
+ }
24719
+ /** PUT re-runs the full create validation: `mission_sets` is REQUIRED again, an omitted `conditions` resets to `[]` (every client) and an omitted `frequency` resets to `every_visit`. */
24720
+ interface UpdateBody {
24721
+ name?: string;
24722
+ mission_sets: RuleMissionSetInput[];
24723
+ conditions?: RuleCondition[];
24724
+ frequency?: RuleFrequencyInput;
24725
+ enabled?: boolean;
24726
+ disabled?: boolean;
24727
+ }
24728
+ namespace Find {
24729
+ /** Results are always ordered `_id` desc — `sort` / `sortPageOrder` are accepted but not applied. */
24730
+ type Params = DefaultPaginationQueryParams & {
24731
+ _id?: StringId | StringId[];
24732
+ name?: string | string[];
24733
+ enabled?: boolean;
24734
+ /** Rules whose `mission_sets` list this set id. */
24735
+ "mission_sets.mission_set"?: StringId | StringId[];
24736
+ /** Case-insensitive regex on `name`. */
24737
+ search?: string;
24738
+ /** Omit to get active AND soft-deleted rows; `false` = active only. */
24739
+ disabled?: boolean;
24740
+ /** ms epoch (or date string); snapped to the start of that day in the company time zone unless `exact_time`. */
24741
+ from_updatedAt?: number;
24742
+ to_updatedAt?: number;
24743
+ from_createdAt?: number;
24744
+ to_createdAt?: number;
24745
+ /** Use the exact instants of the `from_*`/`to_*` bounds instead of whole days. */
24746
+ exact_time?: boolean;
24747
+ };
24748
+ interface Result extends DefaultPaginationResult {
24749
+ data: Data[];
24750
+ }
24751
+ }
24752
+ namespace Get {
24753
+ type ID = StringId;
24754
+ /** The backend answers 400 (not 404) when the id does not exist. */
24755
+ type Result = Data;
24756
+ }
24757
+ namespace Create {
24758
+ type Body = CreateBody;
24759
+ type Result = Data;
24760
+ }
24761
+ namespace Update {
24762
+ type ID = StringId;
24763
+ type Body = UpdateBody;
24764
+ type Result = Data;
24765
+ }
24766
+ namespace Remove {
24767
+ type ID = StringId;
24768
+ /** Soft-delete: the row comes back with `disabled: true` and `editor` stamped. */
24769
+ type Result = Data;
24770
+ }
24771
+ }
24772
+ namespace AiObjectDetectionAssignedMissions {
24773
+ /** What a rule demands of a set's missions: carried by the backend, enforced by the mobile app when the rep ends the visit. */
24774
+ type RequirementMode = "not_required" | "submission_required" | "completion_required";
24775
+ type RuleFrequencyInterval = "every_visit" | "day" | "week" | "month" | "quarter";
24776
+ type RuleConditionKey = "client" | "client_tag" | "client_channel" | "assigned_to" | "chain" | "area_tag" | "team";
24777
+ type RuleConditionOperator = "in" | "nin";
24778
+ interface RuleFrequency {
24779
+ interval: RuleFrequencyInterval;
24780
+ /** Completions required per interval (1 for `every_visit`). */
24781
+ times: number;
24782
+ }
24783
+ /** One rule line with its verdict against the client — the simulator renders exactly why a rule hit or missed. */
24784
+ interface AssignedRuleCondition {
24785
+ key: RuleConditionKey;
24786
+ operator: RuleConditionOperator;
24787
+ value: StringId[];
24788
+ matched: boolean;
24789
+ }
24790
+ interface AssignedRuleMissionSet {
24791
+ mission_set: StringId;
24792
+ /** Absent when the set is disabled / deleted. */
24793
+ set_name?: string;
24794
+ requirement_mode: RequirementMode;
24795
+ }
24796
+ /** One enabled rule resolved against the client (matched or not). */
24797
+ interface AssignedRule {
24798
+ _id: StringId;
24799
+ name: string;
24800
+ /** The sets the rule assigns, each with its demand. */
24801
+ mission_sets: AssignedRuleMissionSet[];
24802
+ /** The STRICTEST mode across the rule's `mission_sets` lines. */
24803
+ requirement_mode: RequirementMode;
24804
+ /** Every condition line holds (AND). Zero lines => true. */
24805
+ matched: boolean;
24806
+ frequency: RuleFrequency;
24807
+ conditions: AssignedRuleCondition[];
24808
+ }
24809
+ /** One demand a matched rule places on a mission through one of its sets. */
24810
+ interface MissionRequirement {
24811
+ rule: StringId;
24812
+ rule_name: string;
24813
+ set: StringId;
24814
+ set_name?: string;
24815
+ frequency: RuleFrequency;
24816
+ requirement_mode: RequirementMode;
24817
+ /** Start (ms) of the first BUSINESS day of the demand's current window; null for a visit-scoped `every_visit` demand (matched by visit id, not by time). */
24818
+ window_start: number | null;
24819
+ /** Completions whose business day falls inside the window (visit-scoped every_visit: those stamped with the visit id). */
24820
+ completions: number;
24821
+ /** completions >= frequency.times (visit-scoped every_visit: >= 1) — regardless of `requirement_mode`. */
24822
+ satisfied: boolean;
24823
+ }
24824
+ interface AssignedMission {
24825
+ _id: StringId;
24826
+ name: string;
24827
+ /** 0..1 — the weighted mission score a scan must reach to complete it. */
24828
+ min_score: number;
24829
+ /** Optional detection category id — the mobile stamps it on the session it starts FROM this mission so the pipeline auto-analyzes it. */
24830
+ category?: StringId;
24831
+ category_name?: string;
24832
+ /** Names of the matched sets that carry this mission. */
24833
+ via_sets: string[];
24834
+ /** The STRICTEST demand across `requirements[]` — what the mobile enforces when the rep ends the visit. */
24835
+ requirement_mode: RequirementMode;
24836
+ /** One per matching rule x set line. */
24837
+ requirements: MissionRequirement[];
24838
+ /** Every requirement satisfied — mode-agnostic (the mobile combines it with `requirement_mode`). */
24839
+ done: boolean;
24840
+ }
24841
+ /** The assignment read for ONE client — computed on every call from current rules / sets / stored mission results; nothing is stored. */
24842
+ interface Data {
24843
+ client: {
24844
+ _id: StringId;
24845
+ name: string;
24846
+ };
24847
+ /** The `rep` query param echoed back (null when not sent). */
24848
+ rep: StringId | null;
24849
+ /** The context rep's `rep_can_redo_object_detection_missions` permission (default false); always true when no rep is in context (admin simulator). */
24850
+ rep_can_redo: boolean;
24851
+ /** IANA time zone the business day was resolved in. */
24852
+ timezone: string;
24853
+ /** `YYYY-MM-DD` — the CURRENT business day the windows are anchored to (rep stamping context when a rep is in scope, else the company's). */
24854
+ business_day: string;
24855
+ /** The device visit id the read is scoped to — null when `visit` was not sent. */
24856
+ visit_id: string | null;
24857
+ /** Every enabled rule with per-condition verdicts (matched or not). */
24858
+ rules: AssignedRule[];
24859
+ /** The ASSIGNED missions only (via matched rules' sets), sorted by name. */
24860
+ missions: AssignedMission[];
24861
+ }
24862
+ namespace Find {
24863
+ /** Not paginated — `per_page` / `page` are ignored. */
24864
+ interface Params {
24865
+ /** REQUIRED — the client to resolve the rules against (400 when missing / invalid / not found). */
24866
+ client: StringId;
24867
+ /** Count only this rep's completions and use the rep's business-day context + redo permission. Defaults to the rep token's rep, else the company context. */
24868
+ rep?: StringId;
24869
+ /** DEVICE visit id (`visits.visit_id`) the rep is in — scopes `every_visit` demands to that visit. Not looked up (the visit may not have synced yet). */
24870
+ visit?: string;
24871
+ }
24872
+ type Result = Data;
24873
+ }
24874
+ namespace Get {
24875
+ /** The CLIENT id — `GET /:id` is the same read as `GET ?client=:id`. */
24876
+ type ID = StringId;
24877
+ interface Params {
24878
+ rep?: StringId;
24879
+ visit?: string;
24880
+ }
24881
+ type Result = Data;
24882
+ }
24883
+ }
24884
+ namespace ObjectDetectionAnalyticsReport {
24885
+ /** `metrics` = one row per metric result; `segments` = one row per unwound share-of-shelf SegmentOutput. */
24886
+ type View = "metrics" | "segments";
24887
+ type MetricType = "adjacent_block" | "facings_count" | "on_shelf_availability" | "share_of_shelf";
24888
+ /** Output family of a metric type (denormalized on every result). */
24889
+ type MetricOutput = "compatibility" | "numerical" | "share_of_shelf";
24890
+ /** Unit share-of-shelf quantities are measured in. */
24891
+ type Measure = "width_cm" | "area_cm2" | "facings";
24892
+ /** Filter keys — the same names grouped rows emit in `drilldown`, so a drilldown round-trips as a filter. `segment` applies to the segments view only. */
24893
+ type FilterKey = "metric" | "type" | "output" | "flag" | "client" | "channel" | "rep" | "team" | "mission" | "segment";
24894
+ /** Resolved to label ids before matching ("rows whose metric involves these labels"); each key ANDs independently. The operator is ignored (always `in`). */
24895
+ type LabelFilterKey = "label" | "label_group" | "product_brand" | "product_category";
24896
+ /** Both names address the result's `createdAt`. */
24897
+ type TimeKey = "time" | "createdAt";
24898
+ /** Time-bucket drilldowns: `YYYY-MM-DD` / `YYYY-MM` / ISO week `GGGG-Www` — each sets the whole time range. */
24899
+ type BucketKey = "business_day" | "month" | "week";
24900
+ type FilterOperator = "in" | "nin" | "eq" | "ne";
24901
+ /** Time presets the filter UI sends without a value (company time zone). */
24902
+ type TimePreset = "today" | "yesterday" | "last_seven_days" | "last_thirty_days" | "last_month" | "last_three_months" | "last_six_months" | "last_twelve_months";
24903
+ interface FilterCriterion {
24904
+ key: FilterKey;
24905
+ /** Default `in`. */
24906
+ operator?: FilterOperator;
24907
+ /** 24-hex ids (cast to ObjectId) for id keys; strings for `type` / `output`; booleans (or "true") for `flag`. Non-id values on id keys are dropped. */
24908
+ value: (StringId | string | boolean)[] | StringId | string | boolean;
24909
+ }
24910
+ interface LabelCriterion {
24911
+ key: LabelFilterKey;
24912
+ operator?: "in";
24913
+ value: StringId[] | StringId;
24914
+ }
24915
+ interface TimeBetweenCriterion {
24916
+ key: TimeKey;
24917
+ operator: "between";
24918
+ /** `[from_ms, to_ms]`. */
24919
+ value: [number, number];
24920
+ }
24921
+ interface TimeBoundCriterion {
24922
+ key: TimeKey;
24923
+ operator: "gte" | "lte";
24924
+ /** ms epoch. */
24925
+ value: number | [number];
24926
+ }
24927
+ interface TimePresetCriterion {
24928
+ key: TimeKey;
24929
+ operator: TimePreset;
24930
+ value?: never;
24931
+ }
24932
+ interface BucketCriterion {
24933
+ key: BucketKey;
24934
+ operator?: "eq" | "in";
24935
+ /** One bucket string, e.g. `"2026-07-15"`, `"2026-07"`, `"2026-W29"`. */
24936
+ value: string | [string];
24937
+ }
24938
+ type Criterion = FilterCriterion | LabelCriterion | TimeBetweenCriterion | TimeBoundCriterion | TimePresetCriterion | BucketCriterion;
24939
+ /** `type` / `output` are metrics-view only, `segment` is segments-view only — keys not accepted by the current view are dropped silently. */
24940
+ type GroupKey = "metric" | "type" | "output" | "client" | "channel" | "rep" | "mission" | "segment" | "business_day" | "month" | "week";
24941
+ /** Sort keys the query-string `sortBy` accepts. */
24942
+ type SortField = "_id" | "time" | "createdAt" | "score" | "answer" | "metric_name" | "client_name" | "channel_name" | "segment_name" | "segment_ratio" | "segment_answer" | "row_count" | "avg_score" | "avg_ratio" | "avg_answer";
24943
+ interface SortOption {
24944
+ /** `options.sort` fields must exist in the report's sort metadata (`sort_fields` of a previous response); unknown fields fall back to the default sort. */
24945
+ field: SortField | (string & {});
24946
+ type: "asc" | "desc";
24947
+ }
24948
+ interface CreateBody {
24949
+ /** Default `metrics`. */
24950
+ view?: View;
24951
+ /** Only `anyOf[0].criteria` is honoured — one AND-ed group. */
24952
+ anyOf?: {
24953
+ criteria: Criterion[];
24954
+ }[];
24955
+ /** Group keys, e.g. `[{ _id: "channel" }, { _id: "month" }]`. Grouped rows carry `drilldown` + aggregates. */
24956
+ group?: {
24957
+ _id: GroupKey;
24958
+ }[];
24959
+ /** Column keys to show (ungrouped rows only). */
24960
+ projection?: string[];
24961
+ /** Optional column override; defaults come from the report metadata (`object-detection-metrics` / `object-detection-segments`). */
24962
+ columns?: ReportColumn[];
24963
+ options?: {
24964
+ /** Rows per page (the query-string `per_page` is NOT honoured here). */
24965
+ limit?: number;
24966
+ page?: number;
24967
+ /** Default: `row_count` desc when grouped, `time` desc when flat. */
24968
+ sort?: SortOption[];
24969
+ /** Default `none`. */
24970
+ totals_summary?: "all" | "page" | "none";
24971
+ };
24972
+ }
24973
+ /** Filter-compatible ids / buckets of a grouped row — spread it as query params (or criteria) on the next call to get the detail rows. */
24974
+ interface Drilldown {
24975
+ metric?: StringId;
24976
+ type?: MetricType;
24977
+ output?: MetricOutput;
24978
+ client?: StringId;
24979
+ channel?: StringId;
24980
+ rep?: StringId;
24981
+ mission?: StringId;
24982
+ segment?: StringId;
24983
+ business_day?: string;
24984
+ month?: string;
24985
+ week?: string;
24986
+ }
24987
+ /** One report row. Flat rows carry the result fields; grouped rows carry `drilldown`, the aggregates and the grouped identity names / buckets. */
24988
+ interface Data {
24989
+ /** Metric result id — flat rows only. */
24990
+ _id?: StringId;
24991
+ /** Result creation time (ms). */
24992
+ time?: number;
24993
+ /** `YYYY-MM-DD` in the company time zone. */
24994
+ business_day?: string;
24995
+ /** `YYYY-MM-DD HH:mm:ss`. */
24996
+ timestamp?: string;
24997
+ /** `YYYY-MM`. */
24998
+ month?: string;
24999
+ /** ISO week `GGGG-Www`. */
25000
+ week?: string;
25001
+ metric_id?: StringId;
25002
+ metric_name?: string;
25003
+ metric_type?: MetricType;
25004
+ output?: MetricOutput;
25005
+ flag?: boolean;
25006
+ /** 0..1, rounded to 4 decimals. */
25007
+ score?: number;
25008
+ client_id?: StringId;
25009
+ client_name?: string;
25010
+ channel_id?: StringId;
25011
+ channel_name?: string;
25012
+ rep_id?: StringId;
25013
+ rep_name?: string;
25014
+ /** The mission the session was STARTED FROM — absent on generic scans. */
25015
+ mission_id?: StringId;
25016
+ mission_name?: string;
25017
+ teams_ids?: StringId[];
25018
+ session_id?: StringId;
25019
+ analysis_id?: StringId;
25020
+ /** metrics view — effective answer (confirmed human override wins): boolean for compatibility, number otherwise. */
25021
+ answer?: boolean | number | null;
25022
+ /** metrics view — effective ratio (availability / main-segment share). */
25023
+ ratio?: number;
25024
+ /** segments view */
25025
+ segment_id?: StringId;
25026
+ segment_name?: string;
25027
+ /** segments view — `main` = the row the target is defined for, else `context`. */
25028
+ is_main?: "main" | "context";
25029
+ /** segments view — measured quantity in `measure` units (2 decimals). */
25030
+ segment_answer?: number;
25031
+ /** segments view — the segment's share 0..1 (4 decimals). */
25032
+ segment_ratio?: number;
25033
+ /** segments view — main rows only. */
25034
+ target_ratio?: number;
25035
+ target_answer?: number;
25036
+ segment_score?: number;
25037
+ measure?: Measure;
25038
+ /** grouped rows */
25039
+ drilldown?: Drilldown;
25040
+ row_count?: number;
25041
+ avg_score?: number;
25042
+ avg_answer?: number;
25043
+ /** grouped, segments view — average share. */
25044
+ avg_ratio?: number;
25045
+ /** grouped, segments view — rows where the segment is the main one. */
25046
+ main_rows?: number;
25047
+ /** grouped, metrics view — flagged results. */
25048
+ flagged?: number;
25049
+ /** grouped by `type` — the metric-type bucket. */
25050
+ type?: MetricType;
25051
+ [key: string]: any;
25052
+ }
25053
+ /** Visible table columns for the current state (grouped vs flat). */
25054
+ interface AnalyticsKey {
25055
+ key: string;
25056
+ /** Translated column label. */
25057
+ value: string;
25058
+ type: "string" | "number";
25059
+ visible: boolean;
25060
+ }
25061
+ /** Present when `options.totals_summary` is `all` (fills `absolute_total`) or `page` (fills `page_total`). Labels: "Rows", "Average Score" (+ "Average Share" on segments). */
25062
+ interface Totals {
25063
+ absolute_total: {
25064
+ [label: string]: number;
25065
+ };
25066
+ page_total: {
25067
+ [label: string]: number;
25068
+ };
25069
+ labels: {
25070
+ key: string;
25071
+ value: string;
25072
+ }[];
25073
+ }
25074
+ /** Returned INSTEAD of rows when the `export` query param is set — the report is queued and emailed to the caller. */
25075
+ interface ExportResult {
25076
+ _id: StringId;
25077
+ success: boolean;
25078
+ msg: string;
25079
+ isExport: boolean;
25080
+ }
25081
+ /** Query keys honoured next to the POST body (drilldown round-trips + export). `per_page` / `page` are NOT honoured — use `options.limit` / `options.page`. */
25082
+ interface QueryParams {
25083
+ /** Fallback when the body has no `view`. */
25084
+ view?: View;
25085
+ metric?: StringId | StringId[];
25086
+ type?: MetricType | MetricType[];
25087
+ output?: MetricOutput | MetricOutput[];
25088
+ flag?: boolean;
25089
+ client?: StringId | StringId[];
25090
+ channel?: StringId | StringId[];
25091
+ rep?: StringId | StringId[];
25092
+ team?: StringId | StringId[];
25093
+ /** The mission the session was started from. */
25094
+ mission?: StringId | StringId[];
25095
+ /** Segments view only. */
25096
+ segment?: StringId | StringId[];
25097
+ label?: StringId | StringId[];
25098
+ label_group?: StringId | StringId[];
25099
+ product_brand?: StringId | StringId[];
25100
+ product_category?: StringId | StringId[];
25101
+ /** ms epoch — every read is time-bounded; default window = the last 30 days. */
25102
+ from_time?: number;
25103
+ to_time?: number;
25104
+ /** `YYYY-MM-DD` — sets the whole range (overrides from_time / to_time). */
25105
+ business_day?: string;
25106
+ /** `YYYY-MM`. */
25107
+ month?: string;
25108
+ /** ISO week `GGGG-Www`. */
25109
+ week?: string;
25110
+ /** Query-string sort (takes precedence over `options.sort`). */
25111
+ sortBy?: {
25112
+ field: SortField;
25113
+ type: "asc" | "desc";
25114
+ }[];
25115
+ /** `excel` schedules an emailed export instead of returning rows — the response is then an `ExportResult`. */
25116
+ export?: "excel";
25117
+ /** Subject / name of the scheduled export email (default "Detection Analytics"). */
25118
+ emailSubject?: string;
25119
+ }
25120
+ interface PaginatedResult extends DefaultPaginationResult {
25121
+ data: Data[];
25122
+ keys: AnalyticsKey[];
25123
+ /** The report's column metadata (sorted by `position`), or the `columns` override echoed back. */
25124
+ columns: ReportColumn[];
25125
+ totals?: Totals;
25126
+ /** The report's sort metadata rows — the valid `options.sort` fields. */
25127
+ sort_fields: ReportSort.Data[];
25128
+ }
25129
+ namespace Find {
25130
+ /** Legacy GET — a thin adapter onto the POST read (`view` + `group` from the query, filters via the same query keys). */
25131
+ type Params = QueryParams & {
25132
+ /** Group keys — array or comma-separated string. */
25133
+ group?: GroupKey | GroupKey[];
25134
+ };
25135
+ type Result = PaginatedResult;
25136
+ }
25137
+ namespace Create {
25138
+ type Params = QueryParams;
25139
+ type Body = CreateBody;
25140
+ type Result = PaginatedResult;
25141
+ }
25142
+ }
20614
25143
  }
20615
25144
  export type StringId = string;
20616
25145
  export type NameSpaces = string[];