@camstack/addon-post-analysis 1.2.193 → 1.2.195

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.
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.158",
39
+ version: "1.2.160",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -45,7 +45,7 @@ async function r() {
45
45
  }
46
46
  },
47
47
  "@camstack/sdk": {
48
- version: "1.2.78",
48
+ version: "1.2.80",
49
49
  scope: "default",
50
50
  shareConfig: {
51
51
  singleton: !0,
@@ -81,7 +81,7 @@ async function r() {
81
81
  }
82
82
  },
83
83
  "@camstack/ui-library": {
84
- version: "1.2.126",
84
+ version: "1.2.128",
85
85
  scope: "default",
86
86
  shareConfig: {
87
87
  singleton: !0,
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-CJKdyCag.js");
5
+ const require_dist = require("../dist-C15zOWA2.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_dist.__toESM(node_fs, 1);
8
8
  let node_path = require("node:path");
@@ -35241,6 +35241,7 @@ var FrameProcessor = class {
35241
35241
  const plateByBbox = /* @__PURE__ */ new Map();
35242
35242
  const maskByBbox = /* @__PURE__ */ new Map();
35243
35243
  const nativeRecoveryByBbox = /* @__PURE__ */ new Map();
35244
+ const birthEvidenceByBbox = /* @__PURE__ */ new Map();
35244
35245
  const flatDetections = frame.detections.filter((d) => d.kind === "first-level").map((det) => {
35245
35246
  const bbox = {
35246
35247
  x: det.bbox.x,
@@ -35256,6 +35257,7 @@ var FrameProcessor = class {
35256
35257
  ...det.nativeRecovery !== void 0 ? { nativeRecovery: det.nativeRecovery } : {}
35257
35258
  };
35258
35259
  if (det.nativeRecovery !== void 0) nativeRecoveryByBbox.set(bbox, det.nativeRecovery);
35260
+ if (det.birthEvidence !== void 0) birthEvidenceByBbox.set(bbox, det.birthEvidence);
35259
35261
  if (det.labels.length > 0) labelsByBbox.set(bbox, det.labels);
35260
35262
  if (det.debug?.originalClassTier !== void 0) originalClassTierByBbox.set(bbox, det.debug.originalClassTier);
35261
35263
  if (det.embedding !== void 0) embeddingByBbox.set(bbox, {
@@ -35481,6 +35483,7 @@ var FrameProcessor = class {
35481
35483
  const plate = plateByBbox.get(td.bbox);
35482
35484
  const sourceDetectionId = sourceIdByBbox.get(td.bbox);
35483
35485
  const nativeRecovery = td.matchedThisFrame !== false ? nativeRecoveryByBbox.get(td.bbox) : void 0;
35486
+ const birthEvidence = td.matchedThisFrame !== false ? birthEvidenceByBbox.get(td.bbox) : void 0;
35484
35487
  return {
35485
35488
  trackId: td.trackId,
35486
35489
  ...sourceDetectionId !== void 0 ? { sourceDetectionId } : {},
@@ -35504,7 +35507,8 @@ var FrameProcessor = class {
35504
35507
  plateScore: plate.score,
35505
35508
  plateBbox: plate.bbox
35506
35509
  } : {},
35507
- ...nativeRecovery !== void 0 ? { nativeRecovery } : {}
35510
+ ...nativeRecovery !== void 0 ? { nativeRecovery } : {},
35511
+ ...birthEvidence !== void 0 ? { birthEvidence } : {}
35508
35512
  };
35509
35513
  });
35510
35514
  this.lastLabelWriteRefusals = refusals;
@@ -45355,9 +45359,28 @@ function withTimeout$2(promise, timeoutMs) {
45355
45359
  });
45356
45360
  }
45357
45361
  async function runConfirmation(candidate, config, deps, exhausted) {
45362
+ const carried = candidate.evidenceCrop ?? null;
45363
+ if (carried) {
45364
+ const verdict = await measure(candidate, carried, config, deps, "carried");
45365
+ if (verdict.verdict === "confirmed" || verdict.reason === "redetect-error") return verdict;
45366
+ }
45358
45367
  let crop = await deps.fetchCrop(candidate);
45359
- if (!crop && exhausted && deps.fetchFallbackCrop) crop = await deps.fetchFallbackCrop(candidate);
45368
+ let cropSource = "native";
45369
+ if (!crop && exhausted && deps.fetchFallbackCrop) {
45370
+ crop = await deps.fetchFallbackCrop(candidate);
45371
+ cropSource = "fallback";
45372
+ }
45360
45373
  if (!crop) return undecided(candidate, "no-crop");
45374
+ return measure(candidate, crop, config, deps, cropSource);
45375
+ }
45376
+ /**
45377
+ * Re-detect on ONE crop and turn the result into a verdict.
45378
+ *
45379
+ * Shared by all three sources so a crop can never be judged by two slightly
45380
+ * different rules depending on where it came from — the `cropSource` argument
45381
+ * is the ONLY thing that varies, and it varies in exactly one place below.
45382
+ */
45383
+ async function measure(candidate, crop, config, deps, cropSource) {
45361
45384
  const detections = await deps.redetect(crop);
45362
45385
  if (detections === null) return undecided(candidate, "redetect-error");
45363
45386
  let best;
@@ -45367,6 +45390,7 @@ async function runConfirmation(candidate, config, deps, exhausted) {
45367
45390
  } else if (!bestIncompatible || d.score > bestIncompatible.score) bestIncompatible = d;
45368
45391
  const bar = barFor(candidate, config, deps);
45369
45392
  const confirmed = best !== void 0 && best.score >= bar;
45393
+ if (!confirmed && cropSource !== "native") return undecided(candidate, cropSource === "fallback" ? "fallback-inconclusive" : "carried-inconclusive");
45370
45394
  const secondary = bestIncompatible ? promoteSecondary(candidate, bestIncompatible, bar, deps) : null;
45371
45395
  return {
45372
45396
  trackId: candidate.trackId,
@@ -45374,6 +45398,7 @@ async function runConfirmation(candidate, config, deps, exhausted) {
45374
45398
  reason: confirmed ? "confirmed" : "suppressed",
45375
45399
  className: candidate.className,
45376
45400
  appliedMinConfidence: bar,
45401
+ cropSource,
45377
45402
  ...best ? { bestScore: best.score } : {},
45378
45403
  ...bestIncompatible ? {
45379
45404
  bestIncompatibleClass: bestIncompatible.macroClass,
@@ -51886,6 +51911,97 @@ async function normalizePhotoOrientation(photo) {
51886
51911
  if (orientation === void 0 || orientation === EXIF_UPRIGHT) return photo;
51887
51912
  return (0, sharp.default)(photo).rotate().jpeg({ quality: 95 }).toBuffer();
51888
51913
  }
51914
+ /**
51915
+ * How long one birth's evidence is kept. Twice `confirmationGateMaxDeferralMs`
51916
+ * (2 000 ms default) — the longest a deferred birth can still be re-offered.
51917
+ */
51918
+ var BIRTH_EVIDENCE_TTL_MS = 4e3;
51919
+ var BirthEvidenceStore = class {
51920
+ slotsPerCamera;
51921
+ ttlMs;
51922
+ byDevice = /* @__PURE__ */ new Map();
51923
+ constructor(slotsPerCamera = 8, ttlMs = BIRTH_EVIDENCE_TTL_MS) {
51924
+ this.slotsPerCamera = slotsPerCamera;
51925
+ this.ttlMs = ttlMs;
51926
+ }
51927
+ /**
51928
+ * Keep this birth's crop.
51929
+ *
51930
+ * The FIRST crop for an id wins. A later frame's crop of the same track is a
51931
+ * different instant, and this store exists precisely to preserve the instant
51932
+ * the birth was measured on — overwriting it would quietly reintroduce the
51933
+ * defect the fallback asymmetry exists to distrust.
51934
+ */
51935
+ remember(deviceKey, trackId, crop, nowMs) {
51936
+ let slots = this.byDevice.get(deviceKey);
51937
+ if (!slots) {
51938
+ slots = /* @__PURE__ */ new Map();
51939
+ this.byDevice.set(deviceKey, slots);
51940
+ }
51941
+ if (slots.has(trackId)) return;
51942
+ slots.set(trackId, {
51943
+ crop,
51944
+ atMs: nowMs
51945
+ });
51946
+ this.evict(deviceKey, slots, nowMs);
51947
+ }
51948
+ /** This birth's crop, or `null` when it was never kept or has aged out. */
51949
+ get(deviceKey, trackId, nowMs) {
51950
+ const entry = this.byDevice.get(deviceKey)?.get(trackId);
51951
+ if (!entry) return null;
51952
+ if (nowMs - entry.atMs > this.ttlMs) {
51953
+ this.resolve(deviceKey, trackId);
51954
+ return null;
51955
+ }
51956
+ return entry.crop;
51957
+ }
51958
+ /** A verdict finally arrived — the crop is dead weight from here. */
51959
+ resolve(deviceKey, trackId) {
51960
+ const slots = this.byDevice.get(deviceKey);
51961
+ if (!slots) return;
51962
+ slots.delete(trackId);
51963
+ if (slots.size === 0) this.byDevice.delete(deviceKey);
51964
+ }
51965
+ /**
51966
+ * Forget every id the tracker is no longer carrying — the same per-frame
51967
+ * contract as `DeferredBirthRegistry.retain`. An id the tracker dropped can
51968
+ * never be decided, so holding its pixels only leaks.
51969
+ */
51970
+ retain(deviceKey, currentTrackIds) {
51971
+ const slots = this.byDevice.get(deviceKey);
51972
+ if (!slots) return;
51973
+ for (const id of [...slots.keys()]) if (!currentTrackIds.has(id)) slots.delete(id);
51974
+ if (slots.size === 0) this.byDevice.delete(deviceKey);
51975
+ }
51976
+ /** Crops held for one camera (diagnostics and tests). */
51977
+ size(deviceKey) {
51978
+ return this.byDevice.get(deviceKey)?.size ?? 0;
51979
+ }
51980
+ /** Bytes held across every camera — the declared budget, checkable. */
51981
+ totalBytes() {
51982
+ let total = 0;
51983
+ for (const slots of this.byDevice.values()) for (const entry of slots.values()) total += entry.crop.byteLength;
51984
+ return total;
51985
+ }
51986
+ /** Drop a camera's reserve wholesale (device removed / pipeline reset). */
51987
+ clearDevice(deviceKey) {
51988
+ this.byDevice.delete(deviceKey);
51989
+ }
51990
+ /**
51991
+ * Age first, then the quota — and only ever inside THIS camera's slots.
51992
+ * `Map` keeps insertion order and a crop is inserted once, so the first key
51993
+ * is always the oldest.
51994
+ */
51995
+ evict(deviceKey, slots, nowMs) {
51996
+ for (const [id, entry] of [...slots]) if (nowMs - entry.atMs > this.ttlMs) slots.delete(id);
51997
+ while (slots.size > this.slotsPerCamera) {
51998
+ const oldest = slots.keys().next().value;
51999
+ if (oldest === void 0) break;
52000
+ slots.delete(oldest);
52001
+ }
52002
+ if (slots.size === 0) this.byDevice.delete(deviceKey);
52003
+ }
52004
+ };
51889
52005
  //#endregion
51890
52006
  //#region src/pipeline-analytics/pipeline/deferred-births.ts
51891
52007
  var DeferredBirthRegistry = class {
@@ -54042,6 +54158,77 @@ function personOverVehicle(person, neighbours, threshold = CONTAINMENT_THRESHOLD
54042
54158
  return best >= threshold ? best : null;
54043
54159
  }
54044
54160
  //#endregion
54161
+ //#region src/pipeline-analytics/pipeline/started-births.ts
54162
+ /**
54163
+ * Memory of births whose ritual has already RUN, per device+source.
54164
+ *
54165
+ * A birth is a decision, and a decision is taken once. Until 2026-09-07 it
54166
+ * could be taken twice, on camera 615 it routinely was, and the second vote
54167
+ * won:
54168
+ *
54169
+ * 04:59:38.335Z confirmation gate: birth confirmed {trackId=f585b2ee…
54170
+ * className=animal bestScore=0.7068}
54171
+ * 04:59:38.335Z track started {trackId=f585b2ee…}
54172
+ * 04:59:38.821Z confirmation gate: birth suppressed (false positive)
54173
+ * {trackId=f585b2ee… className=animal} ← no bestScore:
54174
+ * the second crop held no compatible detection AT ALL
54175
+ * 04:59:38.821Z birth suppressed — track record retracted
54176
+ *
54177
+ * TWO conditions have to meet, and both are structural:
54178
+ *
54179
+ * 1. `processFrame` reads `lastActiveTrackIds` at the top and writes it at the
54180
+ * bottom, and AWAITS the confirmation gate in between (a crop round trip to
54181
+ * the frame's node plus a re-detection: 130–490 ms measured on this camera,
54182
+ * against 100 ms frame spacing). Several frames are therefore in flight at
54183
+ * once and each of them sees the same fresh id as unjudged.
54184
+ * 2. The other birth discriminator, `positionsCount > 1`, does not close the
54185
+ * gap either: a COASTED frame deliberately appends no position
54186
+ * (`TrackStore.upsert`, `matched: false`), so a track the full-frame pass
54187
+ * cannot see — which is the entire premise of the small-subject recovery —
54188
+ * keeps exactly one position for as long as it coasts, and
54189
+ * `classifyTrackAppearance` keeps answering `birth`.
54190
+ *
54191
+ * So the id that has already been born is remembered HERE, and both halves of
54192
+ * the birth path consult it: it is not offered to the gate again, and a verdict
54193
+ * that arrives for it anyway cannot retract it. Same lifetime discipline as
54194
+ * {@link SuppressedBirthRegistry} — held while the tracker still carries the
54195
+ * id, forgotten the frame it stops.
54196
+ */
54197
+ var StartedBirthRegistry = class {
54198
+ byDevice = /* @__PURE__ */ new Map();
54199
+ /** Record that this id's birth ritual ran — `start`, firstFrame, the lot. */
54200
+ markStarted(deviceKey, trackId) {
54201
+ let ids = this.byDevice.get(deviceKey);
54202
+ if (!ids) {
54203
+ ids = /* @__PURE__ */ new Set();
54204
+ this.byDevice.set(deviceKey, ids);
54205
+ }
54206
+ ids.add(trackId);
54207
+ }
54208
+ hasStarted(deviceKey, trackId) {
54209
+ return this.byDevice.get(deviceKey)?.has(trackId) === true;
54210
+ }
54211
+ /**
54212
+ * Forget every started id the tracker is no longer carrying. Called once per
54213
+ * frame with the ids present THIS frame — an id the tracker has dropped can
54214
+ * never be re-offered as a birth, so holding it serves nothing.
54215
+ */
54216
+ retain(deviceKey, currentTrackIds) {
54217
+ const ids = this.byDevice.get(deviceKey);
54218
+ if (!ids) return;
54219
+ for (const id of ids) if (!currentTrackIds.has(id)) ids.delete(id);
54220
+ if (ids.size === 0) this.byDevice.delete(deviceKey);
54221
+ }
54222
+ /** Started ids currently held for a device — diagnostics and tests. */
54223
+ size(deviceKey) {
54224
+ return this.byDevice.get(deviceKey)?.size ?? 0;
54225
+ }
54226
+ /** Drop a device's memory wholesale (device removed / pipeline reset). */
54227
+ clearDevice(deviceKey) {
54228
+ this.byDevice.delete(deviceKey);
54229
+ }
54230
+ };
54231
+ //#endregion
54045
54232
  //#region src/pipeline-analytics/pipeline/stationary/stationary-evidence.ts
54046
54233
  /**
54047
54234
  * stationary-evidence — how a parked object EARNS a registry entry.
@@ -55314,77 +55501,6 @@ function rowToEntry(id, data) {
55314
55501
  };
55315
55502
  }
55316
55503
  //#endregion
55317
- //#region src/pipeline-analytics/pipeline/started-births.ts
55318
- /**
55319
- * Memory of births whose ritual has already RUN, per device+source.
55320
- *
55321
- * A birth is a decision, and a decision is taken once. Until 2026-09-07 it
55322
- * could be taken twice, on camera 615 it routinely was, and the second vote
55323
- * won:
55324
- *
55325
- * 04:59:38.335Z confirmation gate: birth confirmed {trackId=f585b2ee…
55326
- * className=animal bestScore=0.7068}
55327
- * 04:59:38.335Z track started {trackId=f585b2ee…}
55328
- * 04:59:38.821Z confirmation gate: birth suppressed (false positive)
55329
- * {trackId=f585b2ee… className=animal} ← no bestScore:
55330
- * the second crop held no compatible detection AT ALL
55331
- * 04:59:38.821Z birth suppressed — track record retracted
55332
- *
55333
- * TWO conditions have to meet, and both are structural:
55334
- *
55335
- * 1. `processFrame` reads `lastActiveTrackIds` at the top and writes it at the
55336
- * bottom, and AWAITS the confirmation gate in between (a crop round trip to
55337
- * the frame's node plus a re-detection: 130–490 ms measured on this camera,
55338
- * against 100 ms frame spacing). Several frames are therefore in flight at
55339
- * once and each of them sees the same fresh id as unjudged.
55340
- * 2. The other birth discriminator, `positionsCount > 1`, does not close the
55341
- * gap either: a COASTED frame deliberately appends no position
55342
- * (`TrackStore.upsert`, `matched: false`), so a track the full-frame pass
55343
- * cannot see — which is the entire premise of the small-subject recovery —
55344
- * keeps exactly one position for as long as it coasts, and
55345
- * `classifyTrackAppearance` keeps answering `birth`.
55346
- *
55347
- * So the id that has already been born is remembered HERE, and both halves of
55348
- * the birth path consult it: it is not offered to the gate again, and a verdict
55349
- * that arrives for it anyway cannot retract it. Same lifetime discipline as
55350
- * {@link SuppressedBirthRegistry} — held while the tracker still carries the
55351
- * id, forgotten the frame it stops.
55352
- */
55353
- var StartedBirthRegistry = class {
55354
- byDevice = /* @__PURE__ */ new Map();
55355
- /** Record that this id's birth ritual ran — `start`, firstFrame, the lot. */
55356
- markStarted(deviceKey, trackId) {
55357
- let ids = this.byDevice.get(deviceKey);
55358
- if (!ids) {
55359
- ids = /* @__PURE__ */ new Set();
55360
- this.byDevice.set(deviceKey, ids);
55361
- }
55362
- ids.add(trackId);
55363
- }
55364
- hasStarted(deviceKey, trackId) {
55365
- return this.byDevice.get(deviceKey)?.has(trackId) === true;
55366
- }
55367
- /**
55368
- * Forget every started id the tracker is no longer carrying. Called once per
55369
- * frame with the ids present THIS frame — an id the tracker has dropped can
55370
- * never be re-offered as a birth, so holding it serves nothing.
55371
- */
55372
- retain(deviceKey, currentTrackIds) {
55373
- const ids = this.byDevice.get(deviceKey);
55374
- if (!ids) return;
55375
- for (const id of ids) if (!currentTrackIds.has(id)) ids.delete(id);
55376
- if (ids.size === 0) this.byDevice.delete(deviceKey);
55377
- }
55378
- /** Started ids currently held for a device — diagnostics and tests. */
55379
- size(deviceKey) {
55380
- return this.byDevice.get(deviceKey)?.size ?? 0;
55381
- }
55382
- /** Drop a device's memory wholesale (device removed / pipeline reset). */
55383
- clearDevice(deviceKey) {
55384
- this.byDevice.delete(deviceKey);
55385
- }
55386
- };
55387
- //#endregion
55388
55504
  //#region src/pipeline-analytics/pipeline/suppressed-births.ts
55389
55505
  /**
55390
55506
  * Memory of births the confirmation gate rejected, per device.
@@ -67750,6 +67866,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
67750
67866
  /** Births the gate could not MEASURE, awaiting another look on a later frame. */
67751
67867
  deferredBirths = new DeferredBirthRegistry();
67752
67868
  /**
67869
+ * The pixels each deferred birth arrived with — a RESERVE per camera, evicted
67870
+ * by age inside that camera's own quota (D379).
67871
+ *
67872
+ * Not a shared cap. The path this replaces was one node-wide 4 096-entry LRU
67873
+ * measured pinned full in 6 814 of 6 849 samples over three hours, in which a
67874
+ * busy camera evicted a quiet one's proof and every camera added shortened
67875
+ * every other camera's window. See `pipeline/birth-evidence-store.ts` for the
67876
+ * two measurements the slot count and the TTL are taken from.
67877
+ */
67878
+ birthEvidence = new BirthEvidenceStore();
67879
+ /**
67753
67880
  * Subjects the confirmation gate found in a birth's crop BESIDES the birth
67754
67881
  * itself (a dog in a person's crop), waiting to ride the next frame of the
67755
67882
  * same (device, source) as detections — `pipeline/secondary-subjects.ts`.
@@ -71003,12 +71130,20 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
71003
71130
  const exhaustionNowMs = Date.now();
71004
71131
  const exhaustedIds = /* @__PURE__ */ new Set();
71005
71132
  for (const { id } of bornCandidates) if (this.deferredBirths.exhausted(key, id, exhaustionNowMs, gateSettings.maxAttempts, gateSettings.maxDeferralMs)) exhaustedIds.add(id);
71006
- const outcome = await this.confirmTrackBirths(bornCandidates.map(({ id, t }) => ({
71007
- trackId: id,
71008
- className: t.className,
71009
- bbox: t.bbox,
71010
- ...t.nativeRecovery !== void 0 ? { nativeRecovery: t.nativeRecovery } : {}
71011
- })), {
71133
+ for (const { id, t } of bornCandidates) {
71134
+ if (t.birthEvidence === void 0) continue;
71135
+ this.birthEvidence.remember(key, id, Buffer.from(t.birthEvidence.jpegBase64, "base64"), exhaustionNowMs);
71136
+ }
71137
+ const outcome = await this.confirmTrackBirths(bornCandidates.map(({ id, t }) => {
71138
+ const evidenceCrop = this.birthEvidence.get(key, id, exhaustionNowMs);
71139
+ return {
71140
+ trackId: id,
71141
+ className: t.className,
71142
+ bbox: t.bbox,
71143
+ ...t.nativeRecovery !== void 0 ? { nativeRecovery: t.nativeRecovery } : {},
71144
+ ...evidenceCrop !== null ? { evidenceCrop } : {}
71145
+ };
71146
+ }), {
71012
71147
  deviceId,
71013
71148
  source,
71014
71149
  frameHandle,
@@ -71032,6 +71167,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
71032
71167
  const deferredForMs = this.deferredBirths.elapsedMs(key, id, gateNowMs);
71033
71168
  const deferredAttempts = this.deferredBirths.attemptsFor(key, id);
71034
71169
  this.deferredBirths.resolve(key, id);
71170
+ this.birthEvidence.resolve(key, id);
71035
71171
  if (this.startedBirths.hasStarted(key, id)) {
71036
71172
  this.deferredBirths.resolve(key, id);
71037
71173
  log.info("late birth verdict ignored — the track was already born", { meta: {
@@ -71149,6 +71285,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
71149
71285
  this.suppressedBirths.retain(key, currentTrackIds);
71150
71286
  this.startedBirths.retain(key, currentTrackIds);
71151
71287
  this.deferredBirths.retain(key, currentTrackIds);
71288
+ this.birthEvidence.retain(key, currentTrackIds);
71152
71289
  this.lastActiveTrackIds.set(key, currentTrackIds);
71153
71290
  if (currentTrackIds.size > 0) this.lastTrackActivityMs.set(deviceId, Date.now());
71154
71291
  const stationarySettings = this.stationarySettingsFromCache(deviceId);
@@ -71778,6 +71915,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
71778
71915
  bestIncompatibleScore: decision.bestIncompatibleScore
71779
71916
  } : {},
71780
71917
  minConfidence: config.minConfidence,
71918
+ ...decision.cropSource !== void 0 ? { cropSource: decision.cropSource } : {},
71781
71919
  ...decision.reason === "native-pass" ? {
71782
71920
  nativeRecoveryCropSidePx: candidateNativeRecovery.get(decision.trackId)?.cropSidePx,
71783
71921
  nativeRecoveryViewPx: candidateNativeRecovery.get(decision.trackId)?.viewPx