@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.
@@ -1,4 +1,4 @@
1
- import { $ as audioModeOf, A as NcSnoozeSchema, At as BaseAddon, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as boolean, C as NcConditionDescriptorSchema, Ct as sceneMonitorCapability, D as NcRuleTargetSchema, Dt as videoclipsCapability, E as NcRuleSchema, Et as vectorDimFromBase64, Ft as isDeviceScopedCap, G as TimelapseRulePatchSchema, Gt as partialRecord, H as SceneMonitorSchema, Ht as literal, I as RECORDING_EXPORT_MAX_READ_BYTES, It as nodePin, J as VISIT_MERGE_GAP_MS, Jt as unknown, K as TimelapseRuleSchema, Kt as record, L as RetrainStatusSchema, Lt as sleep$1, M as NcSystemEventKindSchema, Mt as DeviceType, N as NcTaxonomySchema, Nt as createEvent, O as NcScheduleSchema, Ot as zoneAnalyticsCapability, P as OpsLogEntrySchema, Pt as hydrateSchema, Q as audioMetricsCapability, Rt as _enum, S as NC_TAXONOMY, T as NcRulePatchSchema, Tt as systemEventFilterApplies, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as number, V as SCENE_DIVERGED, Vt as discriminatedUnion, W as TimelapseRuleInputSchema, Wt as object, X as alarmPanelCapability, Y as addonWidgetsSourceCapability, Yt as EventCategory, Z as assertTimelapseCadences, _ as MediaFileKindEnum, _t as pickClusterStepModels, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as readDeviceStateFrom, c as DETECTION_MACRO_CLASSES, ct as faceGalleryCapability, d as EVENT_KIND_BY_CAP, dt as isDetectionMacroClass, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isScheduleActive, g as MACRO_LABELS, h as LabelAttributionSchema, ht as notificationRulesCapability, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as CamProfileSchema, k as NcSnoozeInputSchema, kt as errMsg$1, lt as failureContributionCapability, m as FailureCounters, mt as kebabToCamel, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isSourceCap, q as TrackSourceSchema, qt as string, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, v as NC_ALARM_SYSTEM_EVENT_KINDS, vt as pipelineAnalyticsCapability, w as NcRuleInputSchema, wt as subKindsOf, xt as readTimelapseGeneratedAt, y as NC_CONDITION_CATALOG, yt as plateGalleryCapability, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as array } from "../dist-CoI1xhsw.mjs";
1
+ import { $ as audioModeOf, A as NcSnoozeSchema, At as BaseAddon, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as boolean, C as NcConditionDescriptorSchema, Ct as sceneMonitorCapability, D as NcRuleTargetSchema, Dt as videoclipsCapability, E as NcRuleSchema, Et as vectorDimFromBase64, Ft as isDeviceScopedCap, G as TimelapseRulePatchSchema, Gt as partialRecord, H as SceneMonitorSchema, Ht as literal, I as RECORDING_EXPORT_MAX_READ_BYTES, It as nodePin, J as VISIT_MERGE_GAP_MS, Jt as unknown, K as TimelapseRuleSchema, Kt as record, L as RetrainStatusSchema, Lt as sleep$1, M as NcSystemEventKindSchema, Mt as DeviceType, N as NcTaxonomySchema, Nt as createEvent, O as NcScheduleSchema, Ot as zoneAnalyticsCapability, P as OpsLogEntrySchema, Pt as hydrateSchema, Q as audioMetricsCapability, Rt as _enum, S as NC_TAXONOMY, T as NcRulePatchSchema, Tt as systemEventFilterApplies, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as number, V as SCENE_DIVERGED, Vt as discriminatedUnion, W as TimelapseRuleInputSchema, Wt as object, X as alarmPanelCapability, Y as addonWidgetsSourceCapability, Yt as EventCategory, Z as assertTimelapseCadences, _ as MediaFileKindEnum, _t as pickClusterStepModels, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as readDeviceStateFrom, c as DETECTION_MACRO_CLASSES, ct as faceGalleryCapability, d as EVENT_KIND_BY_CAP, dt as isDetectionMacroClass, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isScheduleActive, g as MACRO_LABELS, h as LabelAttributionSchema, ht as notificationRulesCapability, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as CamProfileSchema, k as NcSnoozeInputSchema, kt as errMsg$1, lt as failureContributionCapability, m as FailureCounters, mt as kebabToCamel, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isSourceCap, q as TrackSourceSchema, qt as string, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, v as NC_ALARM_SYSTEM_EVENT_KINDS, vt as pipelineAnalyticsCapability, w as NcRuleInputSchema, wt as subKindsOf, xt as readTimelapseGeneratedAt, y as NC_CONDITION_CATALOG, yt as plateGalleryCapability, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as array } from "../dist-DE64ld3c.mjs";
2
2
  import { t as __exportAll } from "../embedding-encoder/index.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import { promises } from "node:fs";
@@ -35216,6 +35216,7 @@ var FrameProcessor = class {
35216
35216
  const plateByBbox = /* @__PURE__ */ new Map();
35217
35217
  const maskByBbox = /* @__PURE__ */ new Map();
35218
35218
  const nativeRecoveryByBbox = /* @__PURE__ */ new Map();
35219
+ const birthEvidenceByBbox = /* @__PURE__ */ new Map();
35219
35220
  const flatDetections = frame.detections.filter((d) => d.kind === "first-level").map((det) => {
35220
35221
  const bbox = {
35221
35222
  x: det.bbox.x,
@@ -35231,6 +35232,7 @@ var FrameProcessor = class {
35231
35232
  ...det.nativeRecovery !== void 0 ? { nativeRecovery: det.nativeRecovery } : {}
35232
35233
  };
35233
35234
  if (det.nativeRecovery !== void 0) nativeRecoveryByBbox.set(bbox, det.nativeRecovery);
35235
+ if (det.birthEvidence !== void 0) birthEvidenceByBbox.set(bbox, det.birthEvidence);
35234
35236
  if (det.labels.length > 0) labelsByBbox.set(bbox, det.labels);
35235
35237
  if (det.debug?.originalClassTier !== void 0) originalClassTierByBbox.set(bbox, det.debug.originalClassTier);
35236
35238
  if (det.embedding !== void 0) embeddingByBbox.set(bbox, {
@@ -35456,6 +35458,7 @@ var FrameProcessor = class {
35456
35458
  const plate = plateByBbox.get(td.bbox);
35457
35459
  const sourceDetectionId = sourceIdByBbox.get(td.bbox);
35458
35460
  const nativeRecovery = td.matchedThisFrame !== false ? nativeRecoveryByBbox.get(td.bbox) : void 0;
35461
+ const birthEvidence = td.matchedThisFrame !== false ? birthEvidenceByBbox.get(td.bbox) : void 0;
35459
35462
  return {
35460
35463
  trackId: td.trackId,
35461
35464
  ...sourceDetectionId !== void 0 ? { sourceDetectionId } : {},
@@ -35479,7 +35482,8 @@ var FrameProcessor = class {
35479
35482
  plateScore: plate.score,
35480
35483
  plateBbox: plate.bbox
35481
35484
  } : {},
35482
- ...nativeRecovery !== void 0 ? { nativeRecovery } : {}
35485
+ ...nativeRecovery !== void 0 ? { nativeRecovery } : {},
35486
+ ...birthEvidence !== void 0 ? { birthEvidence } : {}
35483
35487
  };
35484
35488
  });
35485
35489
  this.lastLabelWriteRefusals = refusals;
@@ -45281,9 +45285,28 @@ function withTimeout$2(promise, timeoutMs) {
45281
45285
  });
45282
45286
  }
45283
45287
  async function runConfirmation(candidate, config, deps, exhausted) {
45288
+ const carried = candidate.evidenceCrop ?? null;
45289
+ if (carried) {
45290
+ const verdict = await measure(candidate, carried, config, deps, "carried");
45291
+ if (verdict.verdict === "confirmed" || verdict.reason === "redetect-error") return verdict;
45292
+ }
45284
45293
  let crop = await deps.fetchCrop(candidate);
45285
- if (!crop && exhausted && deps.fetchFallbackCrop) crop = await deps.fetchFallbackCrop(candidate);
45294
+ let cropSource = "native";
45295
+ if (!crop && exhausted && deps.fetchFallbackCrop) {
45296
+ crop = await deps.fetchFallbackCrop(candidate);
45297
+ cropSource = "fallback";
45298
+ }
45286
45299
  if (!crop) return undecided(candidate, "no-crop");
45300
+ return measure(candidate, crop, config, deps, cropSource);
45301
+ }
45302
+ /**
45303
+ * Re-detect on ONE crop and turn the result into a verdict.
45304
+ *
45305
+ * Shared by all three sources so a crop can never be judged by two slightly
45306
+ * different rules depending on where it came from — the `cropSource` argument
45307
+ * is the ONLY thing that varies, and it varies in exactly one place below.
45308
+ */
45309
+ async function measure(candidate, crop, config, deps, cropSource) {
45287
45310
  const detections = await deps.redetect(crop);
45288
45311
  if (detections === null) return undecided(candidate, "redetect-error");
45289
45312
  let best;
@@ -45293,6 +45316,7 @@ async function runConfirmation(candidate, config, deps, exhausted) {
45293
45316
  } else if (!bestIncompatible || d.score > bestIncompatible.score) bestIncompatible = d;
45294
45317
  const bar = barFor(candidate, config, deps);
45295
45318
  const confirmed = best !== void 0 && best.score >= bar;
45319
+ if (!confirmed && cropSource !== "native") return undecided(candidate, cropSource === "fallback" ? "fallback-inconclusive" : "carried-inconclusive");
45296
45320
  const secondary = bestIncompatible ? promoteSecondary(candidate, bestIncompatible, bar, deps) : null;
45297
45321
  return {
45298
45322
  trackId: candidate.trackId,
@@ -45300,6 +45324,7 @@ async function runConfirmation(candidate, config, deps, exhausted) {
45300
45324
  reason: confirmed ? "confirmed" : "suppressed",
45301
45325
  className: candidate.className,
45302
45326
  appliedMinConfidence: bar,
45327
+ cropSource,
45303
45328
  ...best ? { bestScore: best.score } : {},
45304
45329
  ...bestIncompatible ? {
45305
45330
  bestIncompatibleClass: bestIncompatible.macroClass,
@@ -51812,6 +51837,97 @@ async function normalizePhotoOrientation(photo) {
51812
51837
  if (orientation === void 0 || orientation === EXIF_UPRIGHT) return photo;
51813
51838
  return sharp(photo).rotate().jpeg({ quality: 95 }).toBuffer();
51814
51839
  }
51840
+ /**
51841
+ * How long one birth's evidence is kept. Twice `confirmationGateMaxDeferralMs`
51842
+ * (2 000 ms default) — the longest a deferred birth can still be re-offered.
51843
+ */
51844
+ var BIRTH_EVIDENCE_TTL_MS = 4e3;
51845
+ var BirthEvidenceStore = class {
51846
+ slotsPerCamera;
51847
+ ttlMs;
51848
+ byDevice = /* @__PURE__ */ new Map();
51849
+ constructor(slotsPerCamera = 8, ttlMs = BIRTH_EVIDENCE_TTL_MS) {
51850
+ this.slotsPerCamera = slotsPerCamera;
51851
+ this.ttlMs = ttlMs;
51852
+ }
51853
+ /**
51854
+ * Keep this birth's crop.
51855
+ *
51856
+ * The FIRST crop for an id wins. A later frame's crop of the same track is a
51857
+ * different instant, and this store exists precisely to preserve the instant
51858
+ * the birth was measured on — overwriting it would quietly reintroduce the
51859
+ * defect the fallback asymmetry exists to distrust.
51860
+ */
51861
+ remember(deviceKey, trackId, crop, nowMs) {
51862
+ let slots = this.byDevice.get(deviceKey);
51863
+ if (!slots) {
51864
+ slots = /* @__PURE__ */ new Map();
51865
+ this.byDevice.set(deviceKey, slots);
51866
+ }
51867
+ if (slots.has(trackId)) return;
51868
+ slots.set(trackId, {
51869
+ crop,
51870
+ atMs: nowMs
51871
+ });
51872
+ this.evict(deviceKey, slots, nowMs);
51873
+ }
51874
+ /** This birth's crop, or `null` when it was never kept or has aged out. */
51875
+ get(deviceKey, trackId, nowMs) {
51876
+ const entry = this.byDevice.get(deviceKey)?.get(trackId);
51877
+ if (!entry) return null;
51878
+ if (nowMs - entry.atMs > this.ttlMs) {
51879
+ this.resolve(deviceKey, trackId);
51880
+ return null;
51881
+ }
51882
+ return entry.crop;
51883
+ }
51884
+ /** A verdict finally arrived — the crop is dead weight from here. */
51885
+ resolve(deviceKey, trackId) {
51886
+ const slots = this.byDevice.get(deviceKey);
51887
+ if (!slots) return;
51888
+ slots.delete(trackId);
51889
+ if (slots.size === 0) this.byDevice.delete(deviceKey);
51890
+ }
51891
+ /**
51892
+ * Forget every id the tracker is no longer carrying — the same per-frame
51893
+ * contract as `DeferredBirthRegistry.retain`. An id the tracker dropped can
51894
+ * never be decided, so holding its pixels only leaks.
51895
+ */
51896
+ retain(deviceKey, currentTrackIds) {
51897
+ const slots = this.byDevice.get(deviceKey);
51898
+ if (!slots) return;
51899
+ for (const id of [...slots.keys()]) if (!currentTrackIds.has(id)) slots.delete(id);
51900
+ if (slots.size === 0) this.byDevice.delete(deviceKey);
51901
+ }
51902
+ /** Crops held for one camera (diagnostics and tests). */
51903
+ size(deviceKey) {
51904
+ return this.byDevice.get(deviceKey)?.size ?? 0;
51905
+ }
51906
+ /** Bytes held across every camera — the declared budget, checkable. */
51907
+ totalBytes() {
51908
+ let total = 0;
51909
+ for (const slots of this.byDevice.values()) for (const entry of slots.values()) total += entry.crop.byteLength;
51910
+ return total;
51911
+ }
51912
+ /** Drop a camera's reserve wholesale (device removed / pipeline reset). */
51913
+ clearDevice(deviceKey) {
51914
+ this.byDevice.delete(deviceKey);
51915
+ }
51916
+ /**
51917
+ * Age first, then the quota — and only ever inside THIS camera's slots.
51918
+ * `Map` keeps insertion order and a crop is inserted once, so the first key
51919
+ * is always the oldest.
51920
+ */
51921
+ evict(deviceKey, slots, nowMs) {
51922
+ for (const [id, entry] of [...slots]) if (nowMs - entry.atMs > this.ttlMs) slots.delete(id);
51923
+ while (slots.size > this.slotsPerCamera) {
51924
+ const oldest = slots.keys().next().value;
51925
+ if (oldest === void 0) break;
51926
+ slots.delete(oldest);
51927
+ }
51928
+ if (slots.size === 0) this.byDevice.delete(deviceKey);
51929
+ }
51930
+ };
51815
51931
  //#endregion
51816
51932
  //#region src/pipeline-analytics/pipeline/deferred-births.ts
51817
51933
  var DeferredBirthRegistry = class {
@@ -53968,6 +54084,77 @@ function personOverVehicle(person, neighbours, threshold = CONTAINMENT_THRESHOLD
53968
54084
  return best >= threshold ? best : null;
53969
54085
  }
53970
54086
  //#endregion
54087
+ //#region src/pipeline-analytics/pipeline/started-births.ts
54088
+ /**
54089
+ * Memory of births whose ritual has already RUN, per device+source.
54090
+ *
54091
+ * A birth is a decision, and a decision is taken once. Until 2026-09-07 it
54092
+ * could be taken twice, on camera 615 it routinely was, and the second vote
54093
+ * won:
54094
+ *
54095
+ * 04:59:38.335Z confirmation gate: birth confirmed {trackId=f585b2ee…
54096
+ * className=animal bestScore=0.7068}
54097
+ * 04:59:38.335Z track started {trackId=f585b2ee…}
54098
+ * 04:59:38.821Z confirmation gate: birth suppressed (false positive)
54099
+ * {trackId=f585b2ee… className=animal} ← no bestScore:
54100
+ * the second crop held no compatible detection AT ALL
54101
+ * 04:59:38.821Z birth suppressed — track record retracted
54102
+ *
54103
+ * TWO conditions have to meet, and both are structural:
54104
+ *
54105
+ * 1. `processFrame` reads `lastActiveTrackIds` at the top and writes it at the
54106
+ * bottom, and AWAITS the confirmation gate in between (a crop round trip to
54107
+ * the frame's node plus a re-detection: 130–490 ms measured on this camera,
54108
+ * against 100 ms frame spacing). Several frames are therefore in flight at
54109
+ * once and each of them sees the same fresh id as unjudged.
54110
+ * 2. The other birth discriminator, `positionsCount > 1`, does not close the
54111
+ * gap either: a COASTED frame deliberately appends no position
54112
+ * (`TrackStore.upsert`, `matched: false`), so a track the full-frame pass
54113
+ * cannot see — which is the entire premise of the small-subject recovery —
54114
+ * keeps exactly one position for as long as it coasts, and
54115
+ * `classifyTrackAppearance` keeps answering `birth`.
54116
+ *
54117
+ * So the id that has already been born is remembered HERE, and both halves of
54118
+ * the birth path consult it: it is not offered to the gate again, and a verdict
54119
+ * that arrives for it anyway cannot retract it. Same lifetime discipline as
54120
+ * {@link SuppressedBirthRegistry} — held while the tracker still carries the
54121
+ * id, forgotten the frame it stops.
54122
+ */
54123
+ var StartedBirthRegistry = class {
54124
+ byDevice = /* @__PURE__ */ new Map();
54125
+ /** Record that this id's birth ritual ran — `start`, firstFrame, the lot. */
54126
+ markStarted(deviceKey, trackId) {
54127
+ let ids = this.byDevice.get(deviceKey);
54128
+ if (!ids) {
54129
+ ids = /* @__PURE__ */ new Set();
54130
+ this.byDevice.set(deviceKey, ids);
54131
+ }
54132
+ ids.add(trackId);
54133
+ }
54134
+ hasStarted(deviceKey, trackId) {
54135
+ return this.byDevice.get(deviceKey)?.has(trackId) === true;
54136
+ }
54137
+ /**
54138
+ * Forget every started id the tracker is no longer carrying. Called once per
54139
+ * frame with the ids present THIS frame — an id the tracker has dropped can
54140
+ * never be re-offered as a birth, so holding it serves nothing.
54141
+ */
54142
+ retain(deviceKey, currentTrackIds) {
54143
+ const ids = this.byDevice.get(deviceKey);
54144
+ if (!ids) return;
54145
+ for (const id of ids) if (!currentTrackIds.has(id)) ids.delete(id);
54146
+ if (ids.size === 0) this.byDevice.delete(deviceKey);
54147
+ }
54148
+ /** Started ids currently held for a device — diagnostics and tests. */
54149
+ size(deviceKey) {
54150
+ return this.byDevice.get(deviceKey)?.size ?? 0;
54151
+ }
54152
+ /** Drop a device's memory wholesale (device removed / pipeline reset). */
54153
+ clearDevice(deviceKey) {
54154
+ this.byDevice.delete(deviceKey);
54155
+ }
54156
+ };
54157
+ //#endregion
53971
54158
  //#region src/pipeline-analytics/pipeline/stationary/stationary-evidence.ts
53972
54159
  /**
53973
54160
  * stationary-evidence — how a parked object EARNS a registry entry.
@@ -55240,77 +55427,6 @@ function rowToEntry(id, data) {
55240
55427
  };
55241
55428
  }
55242
55429
  //#endregion
55243
- //#region src/pipeline-analytics/pipeline/started-births.ts
55244
- /**
55245
- * Memory of births whose ritual has already RUN, per device+source.
55246
- *
55247
- * A birth is a decision, and a decision is taken once. Until 2026-09-07 it
55248
- * could be taken twice, on camera 615 it routinely was, and the second vote
55249
- * won:
55250
- *
55251
- * 04:59:38.335Z confirmation gate: birth confirmed {trackId=f585b2ee…
55252
- * className=animal bestScore=0.7068}
55253
- * 04:59:38.335Z track started {trackId=f585b2ee…}
55254
- * 04:59:38.821Z confirmation gate: birth suppressed (false positive)
55255
- * {trackId=f585b2ee… className=animal} ← no bestScore:
55256
- * the second crop held no compatible detection AT ALL
55257
- * 04:59:38.821Z birth suppressed — track record retracted
55258
- *
55259
- * TWO conditions have to meet, and both are structural:
55260
- *
55261
- * 1. `processFrame` reads `lastActiveTrackIds` at the top and writes it at the
55262
- * bottom, and AWAITS the confirmation gate in between (a crop round trip to
55263
- * the frame's node plus a re-detection: 130–490 ms measured on this camera,
55264
- * against 100 ms frame spacing). Several frames are therefore in flight at
55265
- * once and each of them sees the same fresh id as unjudged.
55266
- * 2. The other birth discriminator, `positionsCount > 1`, does not close the
55267
- * gap either: a COASTED frame deliberately appends no position
55268
- * (`TrackStore.upsert`, `matched: false`), so a track the full-frame pass
55269
- * cannot see — which is the entire premise of the small-subject recovery —
55270
- * keeps exactly one position for as long as it coasts, and
55271
- * `classifyTrackAppearance` keeps answering `birth`.
55272
- *
55273
- * So the id that has already been born is remembered HERE, and both halves of
55274
- * the birth path consult it: it is not offered to the gate again, and a verdict
55275
- * that arrives for it anyway cannot retract it. Same lifetime discipline as
55276
- * {@link SuppressedBirthRegistry} — held while the tracker still carries the
55277
- * id, forgotten the frame it stops.
55278
- */
55279
- var StartedBirthRegistry = class {
55280
- byDevice = /* @__PURE__ */ new Map();
55281
- /** Record that this id's birth ritual ran — `start`, firstFrame, the lot. */
55282
- markStarted(deviceKey, trackId) {
55283
- let ids = this.byDevice.get(deviceKey);
55284
- if (!ids) {
55285
- ids = /* @__PURE__ */ new Set();
55286
- this.byDevice.set(deviceKey, ids);
55287
- }
55288
- ids.add(trackId);
55289
- }
55290
- hasStarted(deviceKey, trackId) {
55291
- return this.byDevice.get(deviceKey)?.has(trackId) === true;
55292
- }
55293
- /**
55294
- * Forget every started id the tracker is no longer carrying. Called once per
55295
- * frame with the ids present THIS frame — an id the tracker has dropped can
55296
- * never be re-offered as a birth, so holding it serves nothing.
55297
- */
55298
- retain(deviceKey, currentTrackIds) {
55299
- const ids = this.byDevice.get(deviceKey);
55300
- if (!ids) return;
55301
- for (const id of ids) if (!currentTrackIds.has(id)) ids.delete(id);
55302
- if (ids.size === 0) this.byDevice.delete(deviceKey);
55303
- }
55304
- /** Started ids currently held for a device — diagnostics and tests. */
55305
- size(deviceKey) {
55306
- return this.byDevice.get(deviceKey)?.size ?? 0;
55307
- }
55308
- /** Drop a device's memory wholesale (device removed / pipeline reset). */
55309
- clearDevice(deviceKey) {
55310
- this.byDevice.delete(deviceKey);
55311
- }
55312
- };
55313
- //#endregion
55314
55430
  //#region src/pipeline-analytics/pipeline/suppressed-births.ts
55315
55431
  /**
55316
55432
  * Memory of births the confirmation gate rejected, per device.
@@ -67676,6 +67792,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
67676
67792
  /** Births the gate could not MEASURE, awaiting another look on a later frame. */
67677
67793
  deferredBirths = new DeferredBirthRegistry();
67678
67794
  /**
67795
+ * The pixels each deferred birth arrived with — a RESERVE per camera, evicted
67796
+ * by age inside that camera's own quota (D379).
67797
+ *
67798
+ * Not a shared cap. The path this replaces was one node-wide 4 096-entry LRU
67799
+ * measured pinned full in 6 814 of 6 849 samples over three hours, in which a
67800
+ * busy camera evicted a quiet one's proof and every camera added shortened
67801
+ * every other camera's window. See `pipeline/birth-evidence-store.ts` for the
67802
+ * two measurements the slot count and the TTL are taken from.
67803
+ */
67804
+ birthEvidence = new BirthEvidenceStore();
67805
+ /**
67679
67806
  * Subjects the confirmation gate found in a birth's crop BESIDES the birth
67680
67807
  * itself (a dog in a person's crop), waiting to ride the next frame of the
67681
67808
  * same (device, source) as detections — `pipeline/secondary-subjects.ts`.
@@ -70929,12 +71056,20 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
70929
71056
  const exhaustionNowMs = Date.now();
70930
71057
  const exhaustedIds = /* @__PURE__ */ new Set();
70931
71058
  for (const { id } of bornCandidates) if (this.deferredBirths.exhausted(key, id, exhaustionNowMs, gateSettings.maxAttempts, gateSettings.maxDeferralMs)) exhaustedIds.add(id);
70932
- const outcome = await this.confirmTrackBirths(bornCandidates.map(({ id, t }) => ({
70933
- trackId: id,
70934
- className: t.className,
70935
- bbox: t.bbox,
70936
- ...t.nativeRecovery !== void 0 ? { nativeRecovery: t.nativeRecovery } : {}
70937
- })), {
71059
+ for (const { id, t } of bornCandidates) {
71060
+ if (t.birthEvidence === void 0) continue;
71061
+ this.birthEvidence.remember(key, id, Buffer.from(t.birthEvidence.jpegBase64, "base64"), exhaustionNowMs);
71062
+ }
71063
+ const outcome = await this.confirmTrackBirths(bornCandidates.map(({ id, t }) => {
71064
+ const evidenceCrop = this.birthEvidence.get(key, id, exhaustionNowMs);
71065
+ return {
71066
+ trackId: id,
71067
+ className: t.className,
71068
+ bbox: t.bbox,
71069
+ ...t.nativeRecovery !== void 0 ? { nativeRecovery: t.nativeRecovery } : {},
71070
+ ...evidenceCrop !== null ? { evidenceCrop } : {}
71071
+ };
71072
+ }), {
70938
71073
  deviceId,
70939
71074
  source,
70940
71075
  frameHandle,
@@ -70958,6 +71093,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
70958
71093
  const deferredForMs = this.deferredBirths.elapsedMs(key, id, gateNowMs);
70959
71094
  const deferredAttempts = this.deferredBirths.attemptsFor(key, id);
70960
71095
  this.deferredBirths.resolve(key, id);
71096
+ this.birthEvidence.resolve(key, id);
70961
71097
  if (this.startedBirths.hasStarted(key, id)) {
70962
71098
  this.deferredBirths.resolve(key, id);
70963
71099
  log.info("late birth verdict ignored — the track was already born", { meta: {
@@ -71075,6 +71211,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
71075
71211
  this.suppressedBirths.retain(key, currentTrackIds);
71076
71212
  this.startedBirths.retain(key, currentTrackIds);
71077
71213
  this.deferredBirths.retain(key, currentTrackIds);
71214
+ this.birthEvidence.retain(key, currentTrackIds);
71078
71215
  this.lastActiveTrackIds.set(key, currentTrackIds);
71079
71216
  if (currentTrackIds.size > 0) this.lastTrackActivityMs.set(deviceId, Date.now());
71080
71217
  const stationarySettings = this.stationarySettingsFromCache(deviceId);
@@ -71704,6 +71841,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
71704
71841
  bestIncompatibleScore: decision.bestIncompatibleScore
71705
71842
  } : {},
71706
71843
  minConfidence: config.minConfidence,
71844
+ ...decision.cropSource !== void 0 ? { cropSource: decision.cropSource } : {},
71707
71845
  ...decision.reason === "native-pass" ? {
71708
71846
  nativeRecoveryCropSidePx: candidateNativeRecovery.get(decision.trackId)?.cropSidePx,
71709
71847
  nativeRecoveryViewPx: candidateNativeRecovery.get(decision.trackId)?.viewPx
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BmbhUxPs.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-if_wK4QI.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-post-analysis",
3
- "version": "1.2.193",
3
+ "version": "1.2.195",
4
4
  "description": "Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
5
5
  "keywords": [
6
6
  "camstack",