@camstack/addon-post-analysis 1.2.289 → 1.2.291

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 (15) hide show
  1. package/dist/{clip-model-registry-JFtcAZEB.js → clip-model-registry-CTY5co6P.js} +472 -27
  2. package/dist/{clip-model-registry-g3-Yz08Y.mjs → clip-model-registry-Cudz--Ql.mjs} +472 -27
  3. package/dist/embedding-encoder/index.js +1 -1
  4. package/dist/embedding-encoder/index.mjs +1 -1
  5. package/dist/pipeline-analytics/_stub.js +2 -2
  6. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D5OpYfsh.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CBHxwCSj.mjs} +2 -2
  7. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-2OwTV_D2.mjs +26 -0
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DAAKQ3sC.mjs +26 -0
  9. package/dist/pipeline-analytics/{hostInit-BsQiXbDx.mjs → hostInit-DSKT2dE4.mjs} +2 -2
  10. package/dist/pipeline-analytics/index.js +322 -59
  11. package/dist/pipeline-analytics/index.mjs +322 -59
  12. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  13. package/package.json +1 -1
  14. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CfU5NAwo.mjs +0 -26
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-r4OU_ahc.mjs +0 -26
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_clip_model_registry = require("../clip-model-registry-JFtcAZEB.js");
5
+ const require_clip_model_registry = require("../clip-model-registry-CTY5co6P.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_clip_model_registry.__toESM(node_fs, 1);
8
8
  let node_path = require("node:path");
@@ -39677,7 +39677,12 @@ var RebuildInputSchema = require_clip_model_registry.object({
39677
39677
  * Milliseconds between tracks; omit for the default, `0` to run flat out.
39678
39678
  * A rebuild shares hub-main's only thread with every live request.
39679
39679
  */
39680
- pacingMs: require_clip_model_registry.number().int().nonnegative().optional()
39680
+ pacingMs: require_clip_model_registry.number().int().nonnegative().optional(),
39681
+ /**
39682
+ * Re-embed ONLY tracks already in the pinned space — to redo vectors cut
39683
+ * from the wrong box (D664). Requires `maxTracks`.
39684
+ */
39685
+ force: require_clip_model_registry.boolean().optional()
39681
39686
  });
39682
39687
  var RebuildStartedSchema = require_clip_model_registry.object({
39683
39688
  started: require_clip_model_registry.boolean(),
@@ -39697,7 +39702,20 @@ var RebuildStatusSchema = require_clip_model_registry.object({
39697
39702
  scanned: require_clip_model_registry.number(),
39698
39703
  rebuilt: require_clip_model_registry.number(),
39699
39704
  missingKeyFrame: require_clip_model_registry.number(),
39705
+ /** No object event at all — its own number since D664. */
39706
+ missingEvent: require_clip_model_registry.number(),
39700
39707
  missingBbox: require_clip_model_registry.number(),
39708
+ /** Which rule placed each judged track's box (D664). */
39709
+ boxSource: require_clip_model_registry.object({
39710
+ subjectBox: require_clip_model_registry.number(),
39711
+ positions: require_clip_model_registry.number(),
39712
+ event: require_clip_model_registry.number(),
39713
+ refused: require_clip_model_registry.number()
39714
+ }),
39715
+ alreadyInSpace: require_clip_model_registry.number(),
39716
+ /** Whether the current / last pass was a `force` pass (D664). */
39717
+ force: require_clip_model_registry.boolean().nullable(),
39718
+ forcedInSpace: require_clip_model_registry.number(),
39701
39719
  /** Refused by the executing node (unreadable key frame / bad box), not broken. */
39702
39720
  notRunnable: require_clip_model_registry.number(),
39703
39721
  /** The pass STOPPED: no node in the cluster could serve the pinned model. */
@@ -40972,7 +40990,7 @@ function toPixelBox(b) {
40972
40990
  h: b.height
40973
40991
  };
40974
40992
  }
40975
- function refused(reason, detail) {
40993
+ function refused$1(reason, detail) {
40976
40994
  return {
40977
40995
  kind: "refused",
40978
40996
  reason,
@@ -41000,8 +41018,8 @@ function comparableFaces(input) {
41000
41018
  /** Refuse before choosing when nothing in the photo can become the sample. */
41001
41019
  function precondition(input) {
41002
41020
  const enrollable = input.faces.filter((f) => f.enrollable && f.embedding !== void 0);
41003
- if (enrollable.length === 0) return refused("no-enrollable-face", `${input.faces.length} face(s) found, none enrollable (${input.faces.map((f) => f.refusedReason ?? "unknown").join(", ")})`);
41004
- if (!enrollable.some((f) => isEnrollableIn(f, input.modelId))) return refused("model-mismatch", `faces were embedded by ${[...new Set(enrollable.map((f) => f.modelId))].join(", ")}, the old sample by ${input.modelId} — re-embed the gallery first`);
41021
+ if (enrollable.length === 0) return refused$1("no-enrollable-face", `${input.faces.length} face(s) found, none enrollable (${input.faces.map((f) => f.refusedReason ?? "unknown").join(", ")})`);
41022
+ if (!enrollable.some((f) => isEnrollableIn(f, input.modelId))) return refused$1("model-mismatch", `faces were embedded by ${[...new Set(enrollable.map((f) => f.modelId))].join(", ")}, the old sample by ${input.modelId} — re-embed the gallery first`);
41005
41023
  return null;
41006
41024
  }
41007
41025
  function chooseByIou(input, oldBox, scored) {
@@ -41010,13 +41028,13 @@ function chooseByIou(input, oldBox, scored) {
41010
41028
  face,
41011
41029
  iou: iou$3(box, toPixelBox(face.bbox))
41012
41030
  })).filter((c) => c.iou >= PHOTO_REENROL_MIN_IOU);
41013
- if (overlapping.length === 0) return refused("no-iou-match", `no face overlaps the old box at IoU ≥ ${PHOTO_REENROL_MIN_IOU}`);
41014
- if (overlapping.length > 1) return refused("ambiguous", `${overlapping.length} faces overlap the old box (IoU ${overlapping.map((c) => c.iou.toFixed(2)).join(", ")})`);
41031
+ if (overlapping.length === 0) return refused$1("no-iou-match", `no face overlaps the old box at IoU ≥ ${PHOTO_REENROL_MIN_IOU}`);
41032
+ if (overlapping.length > 1) return refused$1("ambiguous", `${overlapping.length} faces overlap the old box (IoU ${overlapping.map((c) => c.iou.toFixed(2)).join(", ")})`);
41015
41033
  const [only] = overlapping;
41016
- if (only === void 0) return refused("no-iou-match", "no overlapping face");
41017
- if (!isEnrollableIn(only.face, input.modelId)) return refused("no-enrollable-face", `the face on the old box is not enrollable (${only.face.refusedReason ?? `model ${only.face.modelId ?? "none"}`})`);
41034
+ if (only === void 0) return refused$1("no-iou-match", "no overlapping face");
41035
+ if (!isEnrollableIn(only.face, input.modelId)) return refused$1("no-enrollable-face", `the face on the old box is not enrollable (${only.face.refusedReason ?? `model ${only.face.modelId ?? "none"}`})`);
41018
41036
  const cosine = scored.find((s) => s.face === only.face)?.cosine;
41019
- if (cosine === void 0 || cosine < .5) return refused("below-floor", `the overlapping face scores cosine ${cosine?.toFixed(3) ?? "n/a"} < ${PHOTO_REENROL_COSINE_FLOOR}`);
41037
+ if (cosine === void 0 || cosine < .5) return refused$1("below-floor", `the overlapping face scores cosine ${cosine?.toFixed(3) ?? "n/a"} < ${PHOTO_REENROL_COSINE_FLOOR}`);
41020
41038
  return {
41021
41039
  kind: "chosen",
41022
41040
  face: only.face,
@@ -41027,10 +41045,10 @@ function chooseByIou(input, oldBox, scored) {
41027
41045
  }
41028
41046
  function chooseByCosine(input, scored) {
41029
41047
  const [best, runnerUp] = [...scored].toSorted((a, b) => b.cosine - a.cosine);
41030
- if (best === void 0) return refused("model-mismatch", "no face has a comparable vector");
41031
- if (best.cosine < .5) return refused("below-floor", `best cosine ${best.cosine.toFixed(3)} < ${PHOTO_REENROL_COSINE_FLOOR}`);
41032
- if (runnerUp !== void 0 && runnerUp.cosine >= .5 && best.cosine - runnerUp.cosine < .1) return refused("ambiguous", `two faces clear the floor within ${PHOTO_REENROL_AMBIGUITY_MARGIN}: ${best.cosine.toFixed(3)} vs ${runnerUp.cosine.toFixed(3)}`);
41033
- if (!isEnrollableIn(best.face, input.modelId)) return refused("ambiguous", `the closest face (cosine ${best.cosine.toFixed(3)}) is not enrollable (${best.face.refusedReason ?? "unknown"}) — the enrollable ones may be someone else`);
41048
+ if (best === void 0) return refused$1("model-mismatch", "no face has a comparable vector");
41049
+ if (best.cosine < .5) return refused$1("below-floor", `best cosine ${best.cosine.toFixed(3)} < ${PHOTO_REENROL_COSINE_FLOOR}`);
41050
+ if (runnerUp !== void 0 && runnerUp.cosine >= .5 && best.cosine - runnerUp.cosine < .1) return refused$1("ambiguous", `two faces clear the floor within ${PHOTO_REENROL_AMBIGUITY_MARGIN}: ${best.cosine.toFixed(3)} vs ${runnerUp.cosine.toFixed(3)}`);
41051
+ if (!isEnrollableIn(best.face, input.modelId)) return refused$1("ambiguous", `the closest face (cosine ${best.cosine.toFixed(3)}) is not enrollable (${best.face.refusedReason ?? "unknown"}) — the enrollable ones may be someone else`);
41034
41052
  return {
41035
41053
  kind: "chosen",
41036
41054
  face: best.face,
@@ -53746,11 +53764,10 @@ var EventStore = class {
53746
53764
  let entryId;
53747
53765
  for (const row of rows) {
53748
53766
  const bbox = row.data["bbox"];
53749
- if (bbox !== null && typeof bbox === "object") {
53750
- const bw = "w" in bbox && typeof bbox.w === "number" ? bbox.w : 0;
53751
- const bh = "h" in bbox && typeof bbox.h === "number" ? bbox.h : 0;
53752
- if (bw <= 0 || bh <= 0) continue;
53753
- }
53767
+ if (bbox === null || typeof bbox !== "object") continue;
53768
+ const bw = "w" in bbox && typeof bbox.w === "number" ? bbox.w : 0;
53769
+ const bh = "h" in bbox && typeof bbox.h === "number" ? bbox.h : 0;
53770
+ if (bw <= 0 || bh <= 0) continue;
53754
53771
  const conf = typeof row.data["confidence"] === "number" ? row.data["confidence"] : 0;
53755
53772
  if (conf > bestConf) {
53756
53773
  bestConf = conf;
@@ -62453,6 +62470,26 @@ var EmbeddingDispatcher = class {
62453
62470
  return this.pendingCrops.size;
62454
62471
  }
62455
62472
  };
62473
+ //#endregion
62474
+ //#region src/pipeline-analytics/embedding-rebuild.ts
62475
+ /**
62476
+ * Track sources a rebuild never lists (D664). An audio track and a synthetic
62477
+ * sensor track have no object event and no subject to embed — on the live hub
62478
+ * 6,726 + 180 of 42,254 tracks — and walking them only inflated the skip
62479
+ * counters an operator reads to judge media coverage.
62480
+ */
62481
+ var REBUILD_EXCLUDED_SOURCES = ["audio", "sensor"];
62482
+ /**
62483
+ * One page of the rebuild listing as a `queryRecentIds` query. Pure, so the
62484
+ * exclusion is assertable: dropping it would look identical in every result
62485
+ * except the skip counters.
62486
+ */
62487
+ function rebuildTrackPageQuery(window) {
62488
+ return {
62489
+ ...window,
62490
+ excludeSources: REBUILD_EXCLUDED_SOURCES
62491
+ };
62492
+ }
62456
62493
  /**
62457
62494
  * Re-embed every listed track from its key frame.
62458
62495
  *
@@ -62468,6 +62505,14 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62468
62505
  let recoveredKeyFrame = 0;
62469
62506
  let missingEvent = 0;
62470
62507
  let missingBbox = 0;
62508
+ let boxSubject = 0;
62509
+ let boxPositions = 0;
62510
+ let boxEvent = 0;
62511
+ let boxRefused = 0;
62512
+ let forcedInSpace = 0;
62513
+ let skippedOutOfSpace = 0;
62514
+ const refusalsByDevice = /* @__PURE__ */ new Map();
62515
+ const force = options.force === true;
62471
62516
  let alreadyInSpace = 0;
62472
62517
  let notRunnable = 0;
62473
62518
  let noCapableNode = 0;
@@ -62478,6 +62523,12 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62478
62523
  let budgetExhausted = false;
62479
62524
  /** Set when the cluster ran out of nodes — the pass stops, `complete` is false. */
62480
62525
  let abandoned = null;
62526
+ const boxSourceCounts = () => ({
62527
+ subjectBox: boxSubject,
62528
+ positions: boxPositions,
62529
+ event: boxEvent,
62530
+ refused: boxRefused
62531
+ });
62481
62532
  while (scanned < maxTracks && abandoned === null) {
62482
62533
  const page = await deps.listTrackIdPage(cursor);
62483
62534
  if (page.trackIds.length === 0) {
@@ -62485,9 +62536,14 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62485
62536
  break;
62486
62537
  }
62487
62538
  let pageIds = page.trackIds;
62539
+ let forcedPage = false;
62488
62540
  if (deps.filterOutOfSpace) {
62489
62541
  const split = await deps.filterOutOfSpace(page.trackIds);
62490
- if (split !== null) {
62542
+ if (split !== null && force) {
62543
+ skippedOutOfSpace += split.outOfSpace.length;
62544
+ pageIds = split.inSpace;
62545
+ forcedPage = true;
62546
+ } else if (split !== null) {
62491
62547
  alreadyInSpace += split.alreadyInSpace;
62492
62548
  pageIds = split.outOfSpace;
62493
62549
  }
@@ -62503,6 +62559,7 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62503
62559
  const room = maxTracks - scanned;
62504
62560
  const batch = pageIds.slice(0, Number.isFinite(room) ? room : void 0);
62505
62561
  if (batch.length < pageIds.length) budgetExhausted = true;
62562
+ if (forcedPage) forcedInSpace += batch.length;
62506
62563
  for (const trackId of batch) {
62507
62564
  if (!deps.canDispatch()) {
62508
62565
  abandoned = "no node left in the rotation can serve the pinned model — stopped before loading any further media";
@@ -62521,6 +62578,25 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62521
62578
  missingEvent += 1;
62522
62579
  return;
62523
62580
  }
62581
+ if (source.bbox === null || source.bbox.w <= 0 || source.bbox.h <= 0) {
62582
+ missingBbox += 1;
62583
+ boxRefused += 1;
62584
+ const reason = source.boxSource.kind === "refused" ? source.boxSource.reason : "degenerate-box";
62585
+ const n = (refusalsByDevice.get(source.deviceId) ?? 0) + 1;
62586
+ refusalsByDevice.set(source.deviceId, n);
62587
+ if (n === 1 || n % 100 === 0) deps.logger.warn("embedding rebuild: no box measured on the key frame — track refused (D303)", {
62588
+ tags: { deviceId: source.deviceId },
62589
+ meta: {
62590
+ trackId,
62591
+ reason,
62592
+ refusedOnThisCamera: n
62593
+ }
62594
+ });
62595
+ return;
62596
+ }
62597
+ if (source.boxSource.kind === "subjectBox") boxSubject += 1;
62598
+ else if (source.boxSource.kind === "positions") boxPositions += 1;
62599
+ else boxEvent += 1;
62524
62600
  let keyFrame = source.keyFrame;
62525
62601
  let recovered = false;
62526
62602
  if (keyFrame === null && deps.recoverKeyFrame) {
@@ -62532,10 +62608,6 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62532
62608
  return;
62533
62609
  }
62534
62610
  if (recovered) recoveredKeyFrame += 1;
62535
- if (source.bbox === null || source.bbox.w <= 0 || source.bbox.h <= 0) {
62536
- missingBbox += 1;
62537
- return;
62538
- }
62539
62611
  const outcome = await deps.embed(recovered ? {
62540
62612
  ...source,
62541
62613
  keyFrame
@@ -62603,6 +62675,9 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62603
62675
  recoveredKeyFrame,
62604
62676
  missingEvent,
62605
62677
  missingBbox,
62678
+ boxSource: boxSourceCounts(),
62679
+ forcedInSpace,
62680
+ skippedOutOfSpace,
62606
62681
  alreadyInSpace,
62607
62682
  notRunnable,
62608
62683
  noCapableNode,
@@ -62626,6 +62701,10 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62626
62701
  recoveredKeyFrame,
62627
62702
  missingEvent,
62628
62703
  missingBbox,
62704
+ boxSource: boxSourceCounts(),
62705
+ force,
62706
+ forcedInSpace,
62707
+ skippedOutOfSpace,
62629
62708
  alreadyInSpace,
62630
62709
  notRunnable,
62631
62710
  noCapableNode,
@@ -62640,6 +62719,9 @@ async function rebuildObjectEmbeddings(deps, options = {}) {
62640
62719
  recoveredKeyFrame,
62641
62720
  missingEvent,
62642
62721
  missingBbox,
62722
+ boxSource: boxSourceCounts(),
62723
+ forcedInSpace,
62724
+ skippedOutOfSpace,
62643
62725
  alreadyInSpace,
62644
62726
  notRunnable,
62645
62727
  noCapableNode,
@@ -62996,8 +63078,6 @@ var FaceSettingsSchema = require_clip_model_registry.object({
62996
63078
  * (2026-07-23), then →0.12 (2026-08-18) alongside the threshold retune —
62997
63079
  * same story and the same recalibration plan as {@link similarityThreshold}. */
62998
63080
  margin: require_clip_model_registry.number().min(0).max(1).default(.12),
62999
- /** Minimum face-detection confidence for a face to be considered. */
63000
- minFaceConfidence: require_clip_model_registry.number().min(0).max(1).default(.5),
63001
63081
  /**
63002
63082
  * Minimum face bbox size (px, shorter side, NATIVE scale when the runner
63003
63083
  * measured it, else detection-frame space) for a face to be DETECTED and
@@ -63079,7 +63159,6 @@ function resolveFaceSettings(raw) {
63079
63159
  enabled: pick("enabled"),
63080
63160
  similarityThreshold: pick("similarityThreshold"),
63081
63161
  margin: pick("margin"),
63082
- minFaceConfidence: pick("minFaceConfidence"),
63083
63162
  minFacePx: pick("minFacePx"),
63084
63163
  recognitionMinFacePx: pick("recognitionMinFacePx"),
63085
63164
  minIdentitySamples: pick("minIdentitySamples"),
@@ -63152,6 +63231,17 @@ function deriveSuggestedBufferCap(bufferMaxPerDevice) {
63152
63231
  * it from a store.
63153
63232
  */
63154
63233
  var STORED_SUGGESTION_MIN_COSINE_KEY = "suggestionMinCosine";
63234
+ /** The settings key that held the retired recognizer face-confidence bar (D662). */
63235
+ var STORED_MIN_FACE_CONFIDENCE_KEY = "minFaceConfidence";
63236
+ /** Plan the removal of ONE retired key from a settings blob; `null` = absent. */
63237
+ function planRetiredKeyPurge(raw, key) {
63238
+ const was = raw[key];
63239
+ if (was === void 0) return null;
63240
+ return {
63241
+ was,
63242
+ patch: { [key]: void 0 }
63243
+ };
63244
+ }
63155
63245
  /**
63156
63246
  * Plan the removal of a persisted suggestion floor from one settings blob.
63157
63247
  *
@@ -63166,12 +63256,15 @@ var STORED_SUGGESTION_MIN_COSINE_KEY = "suggestionMinCosine";
63166
63256
  * serialisation the blob round-trips through.
63167
63257
  */
63168
63258
  function planStoredSuggestionPurge(raw) {
63169
- const was = raw[STORED_SUGGESTION_MIN_COSINE_KEY];
63170
- if (was === void 0) return null;
63171
- return {
63172
- was,
63173
- patch: { [STORED_SUGGESTION_MIN_COSINE_KEY]: void 0 }
63174
- };
63259
+ return planRetiredKeyPurge(raw, STORED_SUGGESTION_MIN_COSINE_KEY);
63260
+ }
63261
+ /**
63262
+ * Plan the removal of a persisted `minFaceConfidence` (D662). Same contract as
63263
+ * {@link planStoredSuggestionPurge}: absence of the key is the marker, and the
63264
+ * patch unsets with `undefined`.
63265
+ */
63266
+ function planStoredMinFaceConfidencePurge(raw) {
63267
+ return planRetiredKeyPurge(raw, STORED_MIN_FACE_CONFIDENCE_KEY);
63175
63268
  }
63176
63269
  /**
63177
63270
  * Map a detection-frame face short side to the 0..1 `sizeScore` the
@@ -63460,7 +63553,7 @@ var FaceRecognizer = class {
63460
63553
  async processFrame(input) {
63461
63554
  const { settings } = input;
63462
63555
  for (const t of input.tracked) if (t.resurrectionScene === "multi-subject") this.quarantineHeldFace(input.deviceId, t.trackId);
63463
- const candidates = input.tracked.filter((t) => Array.isArray(t.embedding) && t.embedding.length > 0 && typeof t.embeddingModelId === "string" && t.confidence >= settings.minFaceConfidence && (t.nativeFaceShortSidePx ?? (t.faceBbox ? Math.min(t.faceBbox.w, t.faceBbox.h) : void 0) ?? Number.POSITIVE_INFINITY) >= settings.minFacePx);
63556
+ const candidates = input.tracked.filter((t) => Array.isArray(t.embedding) && t.embedding.length > 0 && typeof t.embeddingModelId === "string" && (t.nativeFaceShortSidePx ?? (t.faceBbox ? Math.min(t.faceBbox.w, t.faceBbox.h) : void 0) ?? Number.POSITIVE_INFINITY) >= settings.minFacePx);
63464
63557
  if (candidates.length === 0) return;
63465
63558
  this.deps.logger.debug("face: frame candidates", {
63466
63559
  tags: { deviceId: input.deviceId },
@@ -73027,6 +73120,58 @@ function hasKeyFrame(source) {
73027
73120
  }
73028
73121
  //#endregion
73029
73122
  //#region src/pipeline-analytics/rebuild-source-loader.ts
73123
+ function refused(reason) {
73124
+ return {
73125
+ bbox: null,
73126
+ boxSource: {
73127
+ kind: "refused",
73128
+ reason
73129
+ }
73130
+ };
73131
+ }
73132
+ function normaliseMeasured(box) {
73133
+ if (box.frameWidth <= 0 || box.frameHeight <= 0 || box.w <= 0 || box.h <= 0) return null;
73134
+ return {
73135
+ x: box.x / box.frameWidth,
73136
+ y: box.y / box.frameHeight,
73137
+ w: box.w / box.frameWidth,
73138
+ h: box.h / box.frameHeight
73139
+ };
73140
+ }
73141
+ /**
73142
+ * The box measured on THIS key frame: its own `subjectBox`, else the track
73143
+ * position at exactly its instant, else a named refusal. Never another
73144
+ * frame's box (D303).
73145
+ */
73146
+ async function keyFrameBox(deps, trackId, keyFrame) {
73147
+ const own = (await deps.media.listSubjectBoxesByOwner("track", trackId)).find((row) => row.mediaId === keyFrame.mediaId);
73148
+ if (own !== void 0) {
73149
+ const bbox = normaliseMeasured(own.subjectBox);
73150
+ if (bbox !== null) return {
73151
+ bbox,
73152
+ boxSource: { kind: "subjectBox" }
73153
+ };
73154
+ }
73155
+ const track = await deps.tracks.getPersistedByTrackId(trackId);
73156
+ if (track === null) return refused("track-row-missing");
73157
+ const { frameWidth, frameHeight } = track;
73158
+ if (frameWidth === void 0 || frameHeight === void 0) return refused("track-frame-size-unknown");
73159
+ const at = track.positions.find((p) => p.timestamp === keyFrame.timestamp);
73160
+ if (at === void 0) return refused("no-position-at-key-frame");
73161
+ const bbox = normaliseMeasured({
73162
+ ...at.bbox,
73163
+ frameWidth,
73164
+ frameHeight
73165
+ });
73166
+ if (bbox === null) return refused("no-position-at-key-frame");
73167
+ return {
73168
+ bbox,
73169
+ boxSource: { kind: "positions" }
73170
+ };
73171
+ }
73172
+ function hasBox(event) {
73173
+ return event.bbox !== void 0 && event.bbox.w > 0 && event.bbox.h > 0;
73174
+ }
73030
73175
  /**
73031
73176
  * Assemble one track's rebuild inputs, reading at most ONE blob.
73032
73177
  *
@@ -73034,7 +73179,8 @@ function hasKeyFrame(source) {
73034
73179
  * to describe, and the caller counts it. A track WITH an event but WITHOUT a
73035
73180
  * key frame still returns a source (with `keyFrame: null`) so the caller's
73036
73181
  * `missingKeyFrame` counter stays the one place that decision is made; no blob
73037
- * is read in that case either.
73182
+ * is read in that case either. A key frame with no box measured on it returns
73183
+ * `bbox: null` and a named refusal, also without reading the blob.
73038
73184
  */
73039
73185
  async function loadRebuildSource(deps, trackId) {
73040
73186
  const media = await deps.media.listInfoByOwner("track", trackId);
@@ -73042,26 +73188,44 @@ async function loadRebuildSource(deps, trackId) {
73042
73188
  const cropInfo = media.find((m) => m.kind === "thumbnail");
73043
73189
  const peak = await deps.events.peakForTrack(trackId);
73044
73190
  const events = await deps.events.queryObjectByTrackIds([trackId]);
73045
- const best = events.find((e) => String(e.id) === peak.bestEventId) ?? events[0];
73191
+ const best = events.find((e) => String(e.id) === peak.bestEventId) ?? events.find(hasBox) ?? events[0];
73046
73192
  if (best === void 0) return null;
73047
73193
  const common = {
73048
73194
  trackId,
73049
73195
  deviceId: best.deviceId,
73050
- timestamp: best.timestamp,
73051
73196
  className: best.className,
73052
73197
  confidence: best.confidence ?? 0,
73053
- bbox: normaliseEventBbox(best),
73054
73198
  ...cropInfo !== void 0 ? { mediaId: cropInfo.mediaId } : {}
73055
73199
  };
73056
- if (keyFrameInfo === void 0) return {
73200
+ if (keyFrameInfo === void 0) {
73201
+ const bbox = normaliseEventBbox(best);
73202
+ return {
73203
+ ...common,
73204
+ timestamp: best.timestamp,
73205
+ bbox,
73206
+ boxSource: bbox !== null ? { kind: "event" } : {
73207
+ kind: "refused",
73208
+ reason: "event-without-box"
73209
+ },
73210
+ keyFrame: null
73211
+ };
73212
+ }
73213
+ const box = await keyFrameBox(deps, trackId, keyFrameInfo);
73214
+ const keyed = {
73057
73215
  ...common,
73216
+ timestamp: keyFrameInfo.timestamp,
73217
+ bbox: box.bbox,
73218
+ boxSource: box.boxSource,
73219
+ keyFrameMediaId: keyFrameInfo.mediaId
73220
+ };
73221
+ if (box.bbox === null) return {
73222
+ ...keyed,
73058
73223
  keyFrame: null
73059
73224
  };
73060
73225
  const keyFrameFile = await deps.media.getById(keyFrameInfo.mediaId);
73061
73226
  return {
73062
- ...common,
73063
- keyFrame: keyFrameFile ? Buffer.from(keyFrameFile.base64, "base64") : null,
73064
- keyFrameMediaId: keyFrameInfo.mediaId
73227
+ ...keyed,
73228
+ keyFrame: keyFrameFile ? Buffer.from(keyFrameFile.base64, "base64") : null
73065
73229
  };
73066
73230
  }
73067
73231
  //#endregion
@@ -83610,7 +83774,7 @@ function buildGlobalSettingsSchema(cap) {
83610
83774
  addonId: "pipeline-analytics",
83611
83775
  action: "embedding.rebuildStatus",
83612
83776
  buttonLabel: "Check progress",
83613
- successMessage: "{modelId} → {index}: indexed {indexed} · scanned {scanned} · rebuilt {rebuilt} · no key frame {missingKeyFrame} · not runnable {notRunnable} · failed {failed}"
83777
+ successMessage: "{modelId} → {index}: indexed {indexed} · scanned {scanned} · rebuilt {rebuilt} · no key frame {missingKeyFrame} · no box {missingBbox} · no event {missingEvent} · not runnable {notRunnable} · failed {failed}"
83614
83778
  },
83615
83779
  {
83616
83780
  type: "addon-action-button",
@@ -84162,16 +84326,6 @@ function buildGlobalSettingsSchema(cap) {
84162
84326
  step: .01,
84163
84327
  default: FACE_DEFAULTS.margin
84164
84328
  },
84165
- {
84166
- type: "number",
84167
- key: "minFaceConfidence",
84168
- label: "Min face confidence",
84169
- description: "Minimum face-detection confidence (0–1) for a face to be considered.",
84170
- min: 0,
84171
- max: 1,
84172
- step: .05,
84173
- default: FACE_DEFAULTS.minFaceConfidence
84174
- },
84175
84329
  {
84176
84330
  type: "number",
84177
84331
  key: "minFacePx",
@@ -94951,6 +95105,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
94951
95105
  const deviceIds = await this.retentionDeviceIds();
94952
95106
  await this.migrateRetiredRetentionSettings(deviceIds);
94953
95107
  await this.purgeStoredSuggestionThreshold(deviceIds);
95108
+ await this.purgeStoredMinFaceConfidence(deviceIds);
94954
95109
  await this.runTrackRetentionSweep(deviceIds, now);
94955
95110
  const eventStore = this.eventStore;
94956
95111
  const sensorEventStore = this.sensorEventStore;
@@ -95078,6 +95233,80 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
95078
95233
  * this process, and which devices have. One announcement, not a heartbeat. */
95079
95234
  storedSuggestionCheckedGlobal = false;
95080
95235
  storedSuggestionCheckedDevices = /* @__PURE__ */ new Set();
95236
+ storedMinFaceConfidenceCheckedGlobal = false;
95237
+ storedMinFaceConfidenceCheckedDevices = /* @__PURE__ */ new Set();
95238
+ /**
95239
+ * One-time: announce the recognizer face-confidence bar D662 retired, then
95240
+ * unset it — globally and per device. Same shape as
95241
+ * {@link purgeStoredSuggestionThreshold}: absence of the key is the marker,
95242
+ * the write goes through `updateGlobalSettings` / `writeDeviceStore`, and the
95243
+ * patch unsets with `undefined`.
95244
+ *
95245
+ * Why it must leave the store and not merely go unread: the bar compared the
95246
+ * DETECTOR's score without knowing which detector produced it, so its units
95247
+ * changed under it when YuNet became the default (D645). A number left in the
95248
+ * blob is an authority the next reader who greps for it would re-adopt — at a
95249
+ * scale nobody can name. The hub carried an interim 0.85 set by hand on
95250
+ * 2026-09-27; the one bar in force is now the detector model's own floor.
95251
+ */
95252
+ async purgeStoredMinFaceConfidence(deviceIds) {
95253
+ const settings = this.ctxIfReady?.settings;
95254
+ if (!settings) return;
95255
+ if (!this.storedMinFaceConfidenceCheckedGlobal) {
95256
+ this.storedMinFaceConfidenceCheckedGlobal = true;
95257
+ try {
95258
+ const plan = planStoredMinFaceConfidencePurge(await this.resolveGlobalStore());
95259
+ if (plan !== null) {
95260
+ this.announceRetiredFaceConfidenceBar({
95261
+ scope: "global",
95262
+ was: plan.was
95263
+ });
95264
+ await this.updateGlobalSettings(plan.patch);
95265
+ }
95266
+ } catch (err) {
95267
+ this.storedMinFaceConfidenceCheckedGlobal = false;
95268
+ this.ctx.logger.warn("face confidence bar purge failed — will retry", { meta: {
95269
+ scope: "global",
95270
+ error: String(err)
95271
+ } });
95272
+ }
95273
+ }
95274
+ for (const deviceId of deviceIds) {
95275
+ if (this.storedMinFaceConfidenceCheckedDevices.has(deviceId)) continue;
95276
+ this.storedMinFaceConfidenceCheckedDevices.add(deviceId);
95277
+ try {
95278
+ const plan = planStoredMinFaceConfidencePurge(await this.readDeviceBlob(deviceId));
95279
+ if (plan === null) continue;
95280
+ this.announceRetiredFaceConfidenceBar({
95281
+ scope: "device",
95282
+ was: plan.was,
95283
+ deviceId
95284
+ });
95285
+ await settings.writeDeviceStore(deviceId, plan.patch);
95286
+ this.rawDeviceBlobMirror.invalidate(deviceId);
95287
+ } catch (err) {
95288
+ this.storedMinFaceConfidenceCheckedDevices.delete(deviceId);
95289
+ this.ctx.logger.warn("face confidence bar purge failed — will retry", {
95290
+ tags: { deviceId },
95291
+ meta: {
95292
+ scope: "device",
95293
+ error: String(err)
95294
+ }
95295
+ });
95296
+ }
95297
+ }
95298
+ }
95299
+ announceRetiredFaceConfidenceBar(found) {
95300
+ this.ctx.logger.warn("face confidence bar RETIRED — the detector model floor on the runner is the one bar now", {
95301
+ ...found.deviceId !== void 0 ? { tags: { deviceId: found.deviceId } } : {},
95302
+ meta: {
95303
+ scope: found.scope,
95304
+ setting: STORED_MIN_FACE_CONFIDENCE_KEY,
95305
+ wasSetTo: found.was,
95306
+ why: "the recognizer compared the detector score without knowing which detector ran; after YuNet became the default its 0.5 was SCRFD’s bar on YuNet’s scale (D662)."
95307
+ }
95308
+ });
95309
+ }
95081
95310
  /**
95082
95311
  * One-time: announce the suggestion floor D222 retired, then unset it —
95083
95312
  * globally and per device.
@@ -97132,6 +97361,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97132
97361
  refusal: "stores-unavailable"
97133
97362
  };
97134
97363
  }
97364
+ const force = input.force === true;
97365
+ if (force && input.maxTracks === void 0) {
97366
+ this.ctx.logger.warn("rebuildObjectEmbeddings: refusing a force pass with no maxTracks", { meta: { refusal: "force-requires-max-tracks" } });
97367
+ return {
97368
+ started: false,
97369
+ alreadyRunning: false,
97370
+ refusal: "force-requires-max-tracks"
97371
+ };
97372
+ }
97135
97373
  const confirmation = await this.getClusterPinConfirmer(api, "clip-rebuild").confirm();
97136
97374
  if (confirmation.kind === "refused") {
97137
97375
  this.ctx.logger.warn("rebuildObjectEmbeddings: the cluster CLIP model is not confirmed — refusing to start a pass that cannot pin one", { meta: { refusal: confirmation.reason } });
@@ -97175,16 +97413,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97175
97413
  logger: this.ctx.logger
97176
97414
  }));
97177
97415
  const deps = {
97178
- listTrackIdPage: async (cursor) => trackStore.queryRecentIds({
97416
+ listTrackIdPage: async (cursor) => trackStore.queryRecentIds(rebuildTrackPageQuery({
97179
97417
  deviceIds,
97180
97418
  ...input.since !== void 0 ? { since: input.since } : {},
97181
97419
  ...input.until !== void 0 ? { until: input.until } : {},
97182
97420
  ...cursor !== null ? { cursor } : {},
97183
97421
  limit: REBUILD_PAGE_SIZE
97184
- }),
97422
+ })),
97185
97423
  loadSource: (trackId) => loadRebuildSource({
97186
97424
  media: mediaStore,
97187
- events: eventStore
97425
+ events: eventStore,
97426
+ tracks: trackStore
97188
97427
  }, trackId),
97189
97428
  recoverKeyFrame: async (source) => {
97190
97429
  const recovered = await recordedStills.recover(source.deviceId, source.timestamp, RECOVERED_KEY_FRAME_MAX_WIDTH);
@@ -97210,7 +97449,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97210
97449
  if (split === null) return null;
97211
97450
  return {
97212
97451
  outOfSpace: split.outOfSpaceIds,
97213
- alreadyInSpace: split.inSpace
97452
+ alreadyInSpace: split.inSpace,
97453
+ inSpace: split.inSpaceIds
97214
97454
  };
97215
97455
  },
97216
97456
  logger: {
@@ -97222,6 +97462,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97222
97462
  running: true,
97223
97463
  modelId,
97224
97464
  index,
97465
+ force,
97225
97466
  startedAtMs: Date.now(),
97226
97467
  progress: {
97227
97468
  scanned: 0,
@@ -97230,6 +97471,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97230
97471
  recoveredKeyFrame: 0,
97231
97472
  missingEvent: 0,
97232
97473
  missingBbox: 0,
97474
+ boxSource: {
97475
+ subjectBox: 0,
97476
+ positions: 0,
97477
+ event: 0,
97478
+ refused: 0
97479
+ },
97480
+ forcedInSpace: 0,
97481
+ skippedOutOfSpace: 0,
97233
97482
  alreadyInSpace: 0,
97234
97483
  notRunnable: 0,
97235
97484
  noCapableNode: 0,
@@ -97237,6 +97486,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97237
97486
  }
97238
97487
  };
97239
97488
  rebuildObjectEmbeddings(deps, {
97489
+ force,
97240
97490
  ...input.maxTracks !== void 0 ? { maxTracks: input.maxTracks } : {},
97241
97491
  ...input.pacingMs !== void 0 ? { pacingMs: input.pacingMs } : {},
97242
97492
  onProgress: (progress) => {
@@ -97248,6 +97498,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97248
97498
  running: false,
97249
97499
  modelId,
97250
97500
  index,
97501
+ force,
97251
97502
  startedAtMs: this.rebuildState.startedAtMs ?? Date.now(),
97252
97503
  finishedAtMs: Date.now(),
97253
97504
  progress: outcome,
@@ -97263,6 +97514,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97263
97514
  running: false,
97264
97515
  modelId,
97265
97516
  index,
97517
+ force,
97266
97518
  startedAtMs: this.rebuildState.startedAtMs ?? Date.now(),
97267
97519
  finishedAtMs: Date.now(),
97268
97520
  error: String(err)
@@ -97277,7 +97529,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
97277
97529
  modelId,
97278
97530
  index,
97279
97531
  deviceId: input.deviceId ?? "all",
97280
- maxTracks: input.maxTracks ?? "unbounded"
97532
+ maxTracks: input.maxTracks ?? "unbounded",
97533
+ force
97281
97534
  } });
97282
97535
  return {
97283
97536
  started: true,
@@ -98279,8 +98532,18 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
98279
98532
  running: state.running,
98280
98533
  scanned: p?.scanned ?? 0,
98281
98534
  rebuilt: p?.rebuilt ?? 0,
98282
- missingKeyFrame: (p?.missingKeyFrame ?? 0) + (p?.missingEvent ?? 0),
98535
+ missingKeyFrame: p?.missingKeyFrame ?? 0,
98536
+ missingEvent: p?.missingEvent ?? 0,
98283
98537
  missingBbox: p?.missingBbox ?? 0,
98538
+ boxSource: p?.boxSource ?? {
98539
+ subjectBox: 0,
98540
+ positions: 0,
98541
+ event: 0,
98542
+ refused: 0
98543
+ },
98544
+ alreadyInSpace: p?.alreadyInSpace ?? 0,
98545
+ force: state.force ?? null,
98546
+ forcedInSpace: p?.forcedInSpace ?? 0,
98284
98547
  notRunnable: p?.notRunnable ?? 0,
98285
98548
  noCapableNode: p?.noCapableNode ?? 0,
98286
98549
  failed: p?.failed ?? 0,