@camstack/addon-post-analysis 1.2.44 → 1.2.46

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.
@@ -12169,8 +12169,15 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
12169
12169
  failed += 1;
12170
12170
  continue;
12171
12171
  }
12172
- await deps.store(source, embedding);
12173
- rebuilt += 1;
12172
+ const outcome = await deps.store(source, embedding);
12173
+ if (outcome === "persisted") rebuilt += 1;
12174
+ else {
12175
+ failed += 1;
12176
+ deps.logger.warn("embedding rebuild wrote nothing for a track", { meta: {
12177
+ trackId,
12178
+ outcome
12179
+ } });
12180
+ }
12174
12181
  } catch (err) {
12175
12182
  failed += 1;
12176
12183
  deps.logger.warn("embedding rebuild failed for a track", { meta: {
@@ -20597,6 +20604,14 @@ var ObjectEmbeddingStore = class {
20597
20604
  * write: the provider rejects an upsert into an index it has never seen. A
20598
20605
  * dim change against a populated index is REFUSED rather than wiping it, so
20599
20606
  * this failing is the signal that the encoder model changed.
20607
+ *
20608
+ * `declaredDim` caches the declaration, and that cache is per-PROCESS while
20609
+ * the index lives in whichever addon currently provides `vector-store`. When
20610
+ * the active provider changed under a running hub the cache stayed warm
20611
+ * against an index that no longer existed, and every write from then on threw
20612
+ * `index "object-clip" was never declared` — for hours, until the addon was
20613
+ * restarted by hand. So a failed write clears the cache (see
20614
+ * {@link upsertIfBetter}) and the next one re-declares.
20600
20615
  */
20601
20616
  async ensureIndex(dim) {
20602
20617
  if (this.declaredDim === dim) return true;
@@ -20621,31 +20636,56 @@ var ObjectEmbeddingStore = class {
20621
20636
  * Upsert the best-confidence embedding for a track. At most ONE row per
20622
20637
  * track: a write only replaces the stored row when its confidence is
20623
20638
  * strictly higher, keeping the track's best crop over its lifetime.
20639
+ *
20640
+ * `force` BYPASSES that gate — it does not try to out-score it. A rebuild
20641
+ * first attempted to win by writing `confidence: Infinity`, which worked
20642
+ * exactly once: the stored row then held Infinity, `Infinity <= Infinity`
20643
+ * short-circuited, and every later pass silently wrote nothing while
20644
+ * reporting hundreds rebuilt. Observed live — 214 "rebuilt" moved the index
20645
+ * by 4 rows.
20646
+ *
20647
+ * ## Why this returns an outcome instead of `void`
20648
+ *
20649
+ * It used to return `void` and turn every failure into a warn. The rebuild
20650
+ * therefore counted a write the instant the call returned, and the second time
20651
+ * this class of bug appeared it produced `rebuilt: 704` against `indexed: 0` —
20652
+ * a progress number that was pure fiction while the real cause sat in a warn
20653
+ * nobody was reading. The caller now has to distinguish "written" from "logged
20654
+ * and dropped", because a counter that cannot reads as verification.
20624
20655
  */
20625
20656
  async upsertIfBetter(input) {
20626
- if (input.embedding.length === 0) return;
20627
- if (!await this.ensureIndex(input.embedding.length)) return;
20657
+ if (input.embedding.length === 0) return "failed";
20658
+ if (!await this.ensureIndex(input.embedding.length)) return "failed";
20628
20659
  try {
20629
- const stored = (await this.vectorStore.getByIds.query({
20660
+ if (input.force !== true) {
20661
+ const stored = (await this.vectorStore.getByIds.query({
20662
+ index: OBJECT_CLIP_INDEX,
20663
+ ids: [input.trackId]
20664
+ })).items[0]?.metadata["confidence"];
20665
+ if (typeof stored === "number" && input.confidence <= stored) return "skipped";
20666
+ }
20667
+ const result = await this.vectorStore.upsert.mutate({
20630
20668
  index: OBJECT_CLIP_INDEX,
20631
- ids: [input.trackId]
20632
- })).items[0]?.metadata["confidence"];
20633
- if (typeof stored === "number" && input.confidence <= stored) return;
20634
- if ((await this.vectorStore.upsert.mutate({
20635
- index: "object-clip",
20636
20669
  items: [{
20637
20670
  id: input.trackId,
20638
20671
  vector: require_dist.encodeVectorBase64(input.embedding),
20639
20672
  metadata: metadataFor(input)
20640
20673
  }]
20641
- })).rejected > 0) this.logger.warn("object-embedding upsert rejected", {
20642
- tags: { deviceId: input.deviceId },
20643
- meta: {
20644
- trackId: input.trackId,
20645
- dim: input.embedding.length
20646
- }
20647
20674
  });
20675
+ if (result.upserted === 0) {
20676
+ this.logger.warn("object-embedding upsert rejected", {
20677
+ tags: { deviceId: input.deviceId },
20678
+ meta: {
20679
+ trackId: input.trackId,
20680
+ dim: input.embedding.length,
20681
+ rejected: result.rejected
20682
+ }
20683
+ });
20684
+ return "failed";
20685
+ }
20686
+ return "persisted";
20648
20687
  } catch (err) {
20688
+ this.declaredDim = null;
20649
20689
  this.logger.warn("ObjectEmbeddingStore.upsertIfBetter failed", {
20650
20690
  tags: { deviceId: input.deviceId },
20651
20691
  meta: {
@@ -20653,6 +20693,7 @@ var ObjectEmbeddingStore = class {
20653
20693
  error: String(err)
20654
20694
  }
20655
20695
  });
20696
+ return "failed";
20656
20697
  }
20657
20698
  }
20658
20699
  /**
@@ -27104,19 +27145,18 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
27104
27145
  height
27105
27146
  })).embedding;
27106
27147
  },
27107
- store: async (source, embedding) => {
27108
- await store.upsertIfBetter({
27109
- trackId: source.trackId,
27110
- deviceId: source.deviceId,
27111
- timestamp: source.timestamp,
27112
- className: source.className,
27113
- embedding,
27114
- modelId,
27115
- confidence: Number.POSITIVE_INFINITY,
27116
- ...source.mediaKey !== void 0 ? { mediaKey: source.mediaKey } : {},
27117
- ...source.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: source.keyFrameMediaKey } : {}
27118
- });
27119
- },
27148
+ store: async (source, embedding) => store.upsertIfBetter({
27149
+ trackId: source.trackId,
27150
+ deviceId: source.deviceId,
27151
+ timestamp: source.timestamp,
27152
+ className: source.className,
27153
+ embedding,
27154
+ modelId,
27155
+ confidence: source.confidence,
27156
+ force: true,
27157
+ ...source.mediaKey !== void 0 ? { mediaKey: source.mediaKey } : {},
27158
+ ...source.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: source.keyFrameMediaKey } : {}
27159
+ }),
27120
27160
  logger: {
27121
27161
  info: (message, extras) => this.ctx.logger.info(message, extras),
27122
27162
  warn: (message, extras) => this.ctx.logger.warn(message, extras)
@@ -12163,8 +12163,15 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
12163
12163
  failed += 1;
12164
12164
  continue;
12165
12165
  }
12166
- await deps.store(source, embedding);
12167
- rebuilt += 1;
12166
+ const outcome = await deps.store(source, embedding);
12167
+ if (outcome === "persisted") rebuilt += 1;
12168
+ else {
12169
+ failed += 1;
12170
+ deps.logger.warn("embedding rebuild wrote nothing for a track", { meta: {
12171
+ trackId,
12172
+ outcome
12173
+ } });
12174
+ }
12168
12175
  } catch (err) {
12169
12176
  failed += 1;
12170
12177
  deps.logger.warn("embedding rebuild failed for a track", { meta: {
@@ -20591,6 +20598,14 @@ var ObjectEmbeddingStore = class {
20591
20598
  * write: the provider rejects an upsert into an index it has never seen. A
20592
20599
  * dim change against a populated index is REFUSED rather than wiping it, so
20593
20600
  * this failing is the signal that the encoder model changed.
20601
+ *
20602
+ * `declaredDim` caches the declaration, and that cache is per-PROCESS while
20603
+ * the index lives in whichever addon currently provides `vector-store`. When
20604
+ * the active provider changed under a running hub the cache stayed warm
20605
+ * against an index that no longer existed, and every write from then on threw
20606
+ * `index "object-clip" was never declared` — for hours, until the addon was
20607
+ * restarted by hand. So a failed write clears the cache (see
20608
+ * {@link upsertIfBetter}) and the next one re-declares.
20594
20609
  */
20595
20610
  async ensureIndex(dim) {
20596
20611
  if (this.declaredDim === dim) return true;
@@ -20615,31 +20630,56 @@ var ObjectEmbeddingStore = class {
20615
20630
  * Upsert the best-confidence embedding for a track. At most ONE row per
20616
20631
  * track: a write only replaces the stored row when its confidence is
20617
20632
  * strictly higher, keeping the track's best crop over its lifetime.
20633
+ *
20634
+ * `force` BYPASSES that gate — it does not try to out-score it. A rebuild
20635
+ * first attempted to win by writing `confidence: Infinity`, which worked
20636
+ * exactly once: the stored row then held Infinity, `Infinity <= Infinity`
20637
+ * short-circuited, and every later pass silently wrote nothing while
20638
+ * reporting hundreds rebuilt. Observed live — 214 "rebuilt" moved the index
20639
+ * by 4 rows.
20640
+ *
20641
+ * ## Why this returns an outcome instead of `void`
20642
+ *
20643
+ * It used to return `void` and turn every failure into a warn. The rebuild
20644
+ * therefore counted a write the instant the call returned, and the second time
20645
+ * this class of bug appeared it produced `rebuilt: 704` against `indexed: 0` —
20646
+ * a progress number that was pure fiction while the real cause sat in a warn
20647
+ * nobody was reading. The caller now has to distinguish "written" from "logged
20648
+ * and dropped", because a counter that cannot reads as verification.
20618
20649
  */
20619
20650
  async upsertIfBetter(input) {
20620
- if (input.embedding.length === 0) return;
20621
- if (!await this.ensureIndex(input.embedding.length)) return;
20651
+ if (input.embedding.length === 0) return "failed";
20652
+ if (!await this.ensureIndex(input.embedding.length)) return "failed";
20622
20653
  try {
20623
- const stored = (await this.vectorStore.getByIds.query({
20654
+ if (input.force !== true) {
20655
+ const stored = (await this.vectorStore.getByIds.query({
20656
+ index: OBJECT_CLIP_INDEX,
20657
+ ids: [input.trackId]
20658
+ })).items[0]?.metadata["confidence"];
20659
+ if (typeof stored === "number" && input.confidence <= stored) return "skipped";
20660
+ }
20661
+ const result = await this.vectorStore.upsert.mutate({
20624
20662
  index: OBJECT_CLIP_INDEX,
20625
- ids: [input.trackId]
20626
- })).items[0]?.metadata["confidence"];
20627
- if (typeof stored === "number" && input.confidence <= stored) return;
20628
- if ((await this.vectorStore.upsert.mutate({
20629
- index: "object-clip",
20630
20663
  items: [{
20631
20664
  id: input.trackId,
20632
20665
  vector: encodeVectorBase64(input.embedding),
20633
20666
  metadata: metadataFor(input)
20634
20667
  }]
20635
- })).rejected > 0) this.logger.warn("object-embedding upsert rejected", {
20636
- tags: { deviceId: input.deviceId },
20637
- meta: {
20638
- trackId: input.trackId,
20639
- dim: input.embedding.length
20640
- }
20641
20668
  });
20669
+ if (result.upserted === 0) {
20670
+ this.logger.warn("object-embedding upsert rejected", {
20671
+ tags: { deviceId: input.deviceId },
20672
+ meta: {
20673
+ trackId: input.trackId,
20674
+ dim: input.embedding.length,
20675
+ rejected: result.rejected
20676
+ }
20677
+ });
20678
+ return "failed";
20679
+ }
20680
+ return "persisted";
20642
20681
  } catch (err) {
20682
+ this.declaredDim = null;
20643
20683
  this.logger.warn("ObjectEmbeddingStore.upsertIfBetter failed", {
20644
20684
  tags: { deviceId: input.deviceId },
20645
20685
  meta: {
@@ -20647,6 +20687,7 @@ var ObjectEmbeddingStore = class {
20647
20687
  error: String(err)
20648
20688
  }
20649
20689
  });
20690
+ return "failed";
20650
20691
  }
20651
20692
  }
20652
20693
  /**
@@ -27098,19 +27139,18 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
27098
27139
  height
27099
27140
  })).embedding;
27100
27141
  },
27101
- store: async (source, embedding) => {
27102
- await store.upsertIfBetter({
27103
- trackId: source.trackId,
27104
- deviceId: source.deviceId,
27105
- timestamp: source.timestamp,
27106
- className: source.className,
27107
- embedding,
27108
- modelId,
27109
- confidence: Number.POSITIVE_INFINITY,
27110
- ...source.mediaKey !== void 0 ? { mediaKey: source.mediaKey } : {},
27111
- ...source.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: source.keyFrameMediaKey } : {}
27112
- });
27113
- },
27142
+ store: async (source, embedding) => store.upsertIfBetter({
27143
+ trackId: source.trackId,
27144
+ deviceId: source.deviceId,
27145
+ timestamp: source.timestamp,
27146
+ className: source.className,
27147
+ embedding,
27148
+ modelId,
27149
+ confidence: source.confidence,
27150
+ force: true,
27151
+ ...source.mediaKey !== void 0 ? { mediaKey: source.mediaKey } : {},
27152
+ ...source.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: source.keyFrameMediaKey } : {}
27153
+ }),
27114
27154
  logger: {
27115
27155
  info: (message, extras) => this.ctx.logger.info(message, extras),
27116
27156
  warn: (message, extras) => this.ctx.logger.warn(message, extras)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-post-analysis",
3
- "version": "1.2.44",
3
+ "version": "1.2.46",
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",
@@ -87,6 +87,12 @@
87
87
  },
88
88
  {
89
89
  "name": "videoclips"
90
+ },
91
+ {
92
+ "name": "face-gallery"
93
+ },
94
+ {
95
+ "name": "plate-gallery"
90
96
  }
91
97
  ],
92
98
  "storageLocations": [