@camstack/addon-post-analysis 1.2.232 → 1.2.234

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.
@@ -7610,7 +7610,23 @@ var EncodeProfileSchema = object({
7610
7610
  "zerolatency",
7611
7611
  "film",
7612
7612
  "animation"
7613
- ]).optional()
7613
+ ]).optional(),
7614
+ /**
7615
+ * ONE slice per access unit.
7616
+ *
7617
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
7618
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
7619
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
7620
+ * frame-end per frame: the first pictures render and the video then freezes
7621
+ * for good while the audio, on its own plane, plays on. Measured on this hub
7622
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
7623
+ *
7624
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
7625
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
7626
+ * consumers meaning the same thing spelled differently would stop sharing one
7627
+ * child. Absent means "whatever the encoder does" — today's behaviour.
7628
+ */
7629
+ singleSlicePerFrame: boolean().optional()
7614
7630
  }),
7615
7631
  audio: union([literal("passthrough"), object({
7616
7632
  codec: _enum([
@@ -7660,7 +7676,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
7660
7676
  audio: "passthrough"
7661
7677
  };
7662
7678
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7663
- ({ ...BASE_LIVE_EGRESS_PROFILE });
7679
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7664
7680
  /**
7665
7681
  * Deep wiring healthcheck — snapshot of active reachability probes across
7666
7682
  * every declared capability + widget of every installed plugin, on every
@@ -7579,7 +7579,23 @@ var EncodeProfileSchema = object({
7579
7579
  "zerolatency",
7580
7580
  "film",
7581
7581
  "animation"
7582
- ]).optional()
7582
+ ]).optional(),
7583
+ /**
7584
+ * ONE slice per access unit.
7585
+ *
7586
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
7587
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
7588
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
7589
+ * frame-end per frame: the first pictures render and the video then freezes
7590
+ * for good while the audio, on its own plane, plays on. Measured on this hub
7591
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
7592
+ *
7593
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
7594
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
7595
+ * consumers meaning the same thing spelled differently would stop sharing one
7596
+ * child. Absent means "whatever the encoder does" — today's behaviour.
7597
+ */
7598
+ singleSlicePerFrame: boolean().optional()
7583
7599
  }),
7584
7600
  audio: union([literal("passthrough"), object({
7585
7601
  codec: _enum([
@@ -7629,7 +7645,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
7629
7645
  audio: "passthrough"
7630
7646
  };
7631
7647
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7632
- ({ ...BASE_LIVE_EGRESS_PROFILE });
7648
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7633
7649
  /**
7634
7650
  * Deep wiring healthcheck — snapshot of active reachability probes across
7635
7651
  * every declared capability + widget of every installed plugin, on every
@@ -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-Dbf6ceXh.js");
5
+ const require_dist = require("../dist-DTRcLvW5.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_dist.__toESM(node_fs);
8
8
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Dt as parseProcStatus, H as PoolMemoryWatchdog, Pt as resolvePoolMemoryPolicy, Ut as BaseAddon, ft as embeddingEncoderCapability, vt as hfModelUrl } from "../dist-yyQ1H5yE.mjs";
1
+ import { Dt as parseProcStatus, H as PoolMemoryWatchdog, Pt as resolvePoolMemoryPolicy, Ut as BaseAddon, ft as embeddingEncoderCapability, vt as hfModelUrl } from "../dist-Du1qDTXV.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
3
3
  var e = {
4
4
  "@camstack/sdk": {
5
5
  name: "@camstack/sdk",
6
- version: "1.2.103",
6
+ version: "1.2.104",
7
7
  scope: ["default"],
8
8
  loaded: !1,
9
9
  from: "addon_pipeline_analytics_widgets",
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.186",
21
+ version: "1.2.188",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_pipeline_analytics_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.2.152",
36
+ version: "1.2.153",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_pipeline_analytics_widgets",
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.186",
39
+ version: "1.2.188",
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.103",
48
+ version: "1.2.104",
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.152",
84
+ version: "1.2.153",
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-Dbf6ceXh.js");
5
+ const require_dist = require("../dist-DTRcLvW5.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");
@@ -43287,6 +43287,10 @@ function trackColumnsFor(projection, needsPositions = false) {
43287
43287
  * whatever order the engine felt like and a cursor that cannot resume.
43288
43288
  */
43289
43289
  var RECENT_IDS_COLUMNS = ["lastSeen"];
43290
+ /** How many track ids one filter pass may collect per tier. Bounded for the
43291
+ * same reason every bulk read here is: an unbounded scan of this table is
43292
+ * what D447 measured holding the settings thread for seconds. */
43293
+ var TRACK_MATCH_SCAN_LIMIT = 2e4;
43290
43294
  /** Spreadable `columns` for a store query — omitted entirely for `full`. */
43291
43295
  function projectionArg(projection, needsPositions = false) {
43292
43296
  const columns = trackColumnsFor(projection, needsPositions);
@@ -44374,6 +44378,42 @@ var TrackStore = class {
44374
44378
  * current subject out from under it. The orphan audit's grace window would
44375
44379
  * hide this for an hour and then stop hiding it.
44376
44380
  */
44381
+ /**
44382
+ * Track ids whose CLASS, sub-class or instance matches any of `terms`.
44383
+ *
44384
+ * The summaries' filter asks its question here rather than on a copy: a
44385
+ * label is a fact about the TRACK, it is decided while the track lives and
44386
+ * can still be re-decided afterwards (class voting, a late face match, an
44387
+ * operator enrolling a face and renaming that person). A copy stamped onto
44388
+ * the summary would answer with whatever was true at one instant and never
44389
+ * learn — which is the whole reason this lookup exists instead.
44390
+ *
44391
+ * Three queries because the filter compiler has no OR across fields, and one
44392
+ * term can be any of the three tiers: `animal` is a class, `dog` is a
44393
+ * sub-class, `Roberta` is an instance. Bounded, like every other bulk read
44394
+ * here.
44395
+ */
44396
+ async trackIdsMatching(terms) {
44397
+ const wanted = terms.filter((t) => t.length > 0);
44398
+ if (wanted.length === 0) return [];
44399
+ const ids = /* @__PURE__ */ new Set();
44400
+ for (const field of [
44401
+ "className",
44402
+ "label",
44403
+ "subLabel"
44404
+ ]) {
44405
+ const rows = await this.store.query.query({
44406
+ collection: TRACKS_COLLECTION,
44407
+ filter: {
44408
+ whereIn: { [field]: [...wanted] },
44409
+ limit: TRACK_MATCH_SCAN_LIMIT
44410
+ },
44411
+ columns: ["id"]
44412
+ });
44413
+ for (const row of rows) ids.add(row.id);
44414
+ }
44415
+ return [...ids];
44416
+ }
44377
44417
  async existingTrackIds(trackIds) {
44378
44418
  const PAGE = 500;
44379
44419
  const found = /* @__PURE__ */ new Set();
@@ -61476,30 +61516,7 @@ var SUMMARY_MEMBERS_COLUMNS = [
61476
61516
  {
61477
61517
  name: "className",
61478
61518
  type: "TEXT"
61479
- }),
61480
- (
61481
- /**
61482
- * The member's tier-1 label (`dog`, `van`) and tier-2 instance (a name, a
61483
- * plate), STAMPED AT SEAL.
61484
- *
61485
- * Not at the join, and the timing is the whole point: recognition lands
61486
- * during a track's life, almost always after it joined, so a label written at
61487
- * the join would be empty on exactly the members worth naming. At the seal
61488
- * every member's track has closed, so its label is final — there is nothing
61489
- * left to change it.
61490
- *
61491
- * They are COLUMNS because a filter has to be able to ask for them: "show me
61492
- * the arrivals with a dog" is a query over members, and answering it from the
61493
- * tracks would mean reading every track of every summary in the window.
61494
- */
61495
- {
61496
- name: "label",
61497
- type: "TEXT"
61498
- }),
61499
- {
61500
- name: "subLabel",
61501
- type: "TEXT"
61502
- }
61519
+ })
61503
61520
  ];
61504
61521
  var SUMMARY_MEMBERS_INDEXES = [
61505
61522
  {
@@ -61527,16 +61544,6 @@ var SUMMARY_MEMBERS_INDEXES = [
61527
61544
  function readRole(raw) {
61528
61545
  return raw === "primary" || raw === "sibling" || raw === "child" ? raw : null;
61529
61546
  }
61530
- /** The two stamped labels, present only when they hold something. An empty
61531
- * string is not a label and must never reach a filter as one. */
61532
- function labelFields(data) {
61533
- const label = data["label"];
61534
- const subLabel = data["subLabel"];
61535
- return {
61536
- ...typeof label === "string" && label.length > 0 ? { label } : {},
61537
- ...typeof subLabel === "string" && subLabel.length > 0 ? { subLabel } : {}
61538
- };
61539
- }
61540
61547
  function readClassName(raw) {
61541
61548
  return typeof raw === "string" && raw.length > 0 ? raw : null;
61542
61549
  }
@@ -61592,10 +61599,12 @@ var SummaryStore = class {
61592
61599
  store;
61593
61600
  logger;
61594
61601
  onEvict;
61602
+ trackIdsMatching;
61595
61603
  constructor(deps) {
61596
61604
  this.store = deps.store;
61597
61605
  this.logger = deps.logger;
61598
61606
  this.onEvict = deps.onEvict;
61607
+ this.trackIdsMatching = deps.trackIdsMatching;
61599
61608
  }
61600
61609
  static async declare(store) {
61601
61610
  await Promise.all([store.declareCollection.mutate({
@@ -61650,8 +61659,7 @@ var SummaryStore = class {
61650
61659
  deviceId: num$1(r.data["deviceId"]),
61651
61660
  joinedAt: num$1(r.data["timestamp"]),
61652
61661
  role: readRole(r.data["role"]),
61653
- className: readClassName(r.data["className"]),
61654
- ...labelFields(r.data)
61662
+ className: readClassName(r.data["className"])
61655
61663
  }));
61656
61664
  }
61657
61665
  /**
@@ -61839,23 +61847,6 @@ var SummaryStore = class {
61839
61847
  memberCount: leaving.length
61840
61848
  };
61841
61849
  }
61842
- /** Stamp a member's final labels. Best-effort and loud: a missing stamp
61843
- * costs a filter a match, never a membership. */
61844
- async stampMemberLabels(summaryIdValue, trackId, labels) {
61845
- try {
61846
- await this.store.update.mutate({
61847
- collection: SUMMARY_MEMBERS_COLLECTION$1,
61848
- id: memberRowId(summaryIdValue, trackId),
61849
- data: { ...labels }
61850
- });
61851
- } catch (err) {
61852
- this.logger.warn("summary: could not stamp a member label", { meta: {
61853
- summaryId: summaryIdValue,
61854
- trackId,
61855
- error: String(err)
61856
- } });
61857
- }
61858
- }
61859
61850
  /**
61860
61851
  * Put every OTHER member back to `sibling` after a new primary took the role.
61861
61852
  *
@@ -62061,29 +62052,36 @@ var SummaryStore = class {
62061
62052
  /**
62062
62053
  * The summaries whose MEMBERS match any of `classes`, or `null` for no filter.
62063
62054
  *
62064
- * Narrows first, on the members table, and only then reads summary rows.
62065
- * Filtering the other way round — a page of summaries, then their members —
62066
- * returns a short or empty page while matches sit further down the list, and
62067
- * the operator reads that as "there were none".
62055
+ * TRACKS first, then the members that name them, then the summaries. The
62056
+ * label is a fact about the track and it is asked of the track — a summary
62057
+ * that stored its own copy would answer with whatever was true when it
62058
+ * sealed, and would never learn that a face was enrolled and a person named
62059
+ * an hour later. The operator's rule: *a summary keeps the minimum
62060
+ * information necessary*, and a duplicated label is not the minimum, it is a
62061
+ * second truth with an expiry date.
62068
62062
  *
62069
- * Three queries because the filter compiler has no OR across fields, and one
62070
- * term can be any of the three tiers: `animal` is a class, `dog` is a
62071
- * sub-class, `Roberta` is an instance. The caller does not have to know
62072
- * which — that is the whole point of the operator's example.
62063
+ * Narrows before the page is cut. Filtering the other way round — a page of
62064
+ * summaries, then their members — returns a short or empty page while matches
62065
+ * sit further down the list, and the operator reads that as "there were none".
62073
62066
  */
62074
62067
  async summaryIdsMatching(classes) {
62075
62068
  const wanted = (classes ?? []).filter((t) => t.length > 0);
62076
62069
  if (wanted.length === 0) return null;
62070
+ const matchTracks = this.trackIdsMatching;
62071
+ if (matchTracks === void 0) {
62072
+ this.logger.warn("summary filter: no track matcher wired — refusing to answer unfiltered", { meta: { classes: wanted.length } });
62073
+ return /* @__PURE__ */ new Set();
62074
+ }
62075
+ const trackIds = await matchTracks(wanted);
62076
+ if (trackIds.length === 0) return /* @__PURE__ */ new Set();
62077
62077
  const ids = /* @__PURE__ */ new Set();
62078
- for (const field of [
62079
- "className",
62080
- "label",
62081
- "subLabel"
62082
- ]) {
62078
+ const PAGE = 500;
62079
+ for (let i = 0; i < trackIds.length; i += PAGE) {
62080
+ const chunk = trackIds.slice(i, i + PAGE);
62083
62081
  const rows = await this.store.query.query({
62084
62082
  collection: SUMMARY_MEMBERS_COLLECTION$1,
62085
62083
  filter: {
62086
- whereIn: { [field]: [...wanted] },
62084
+ whereIn: { trackId: [...chunk] },
62087
62085
  limit: MEMBER_MATCH_SCAN_LIMIT
62088
62086
  }
62089
62087
  });
@@ -62111,8 +62109,7 @@ var SummaryStore = class {
62111
62109
  deviceId: num$1(r.data["deviceId"]),
62112
62110
  joinedAt: num$1(r.data["timestamp"]),
62113
62111
  role: readRole(r.data["role"]),
62114
- className: readClassName(r.data["className"]),
62115
- ...labelFields(r.data)
62112
+ className: readClassName(r.data["className"])
62116
62113
  };
62117
62114
  const bucket = out.get(summaryIdValue);
62118
62115
  if (bucket === void 0) out.set(summaryIdValue, [member]);
@@ -72846,7 +72843,6 @@ var SummarySealer = class {
72846
72843
  }
72847
72844
  if (await this.isGroupFinished(summary, members) !== true) return;
72848
72845
  const sealedAt = this.deps.now();
72849
- await this.stampLabels(summary, members);
72850
72846
  try {
72851
72847
  if (await this.deps.summaries.seal(summary, sealedAt)) {
72852
72848
  const members = this.forgetGroup(deviceId, summaryId);
@@ -72879,37 +72875,6 @@ var SummarySealer = class {
72879
72875
  if (members === void 0) groups.set(summaryIdValue, new Set([trackId]));
72880
72876
  else members.add(trackId);
72881
72877
  }
72882
- /**
72883
- * Write each member's final label onto its row.
72884
- *
72885
- * At the seal every member's track has closed, so what the pipeline decided
72886
- * about it is final — this is the first and last moment the stamp can be
72887
- * both complete and correct.
72888
- */
72889
- async stampLabels(summary, members) {
72890
- const read = this.deps.labelsFor;
72891
- if (read === void 0 || members.length === 0) return;
72892
- try {
72893
- const labels = await read(members);
72894
- for (const [trackId, value] of labels) {
72895
- const label = value.label;
72896
- const subLabel = value.subLabel;
72897
- if ((label === void 0 || label.length === 0) && (subLabel === void 0 || subLabel.length === 0)) continue;
72898
- await this.deps.summaries.stampMemberLabels(summary.id, trackId, {
72899
- ...label !== void 0 && label.length > 0 ? { label } : {},
72900
- ...subLabel !== void 0 && subLabel.length > 0 ? { subLabel } : {}
72901
- });
72902
- }
72903
- } catch (err) {
72904
- this.deps.logger.warn("summary: could not stamp member labels — the seal stands", {
72905
- tags: { deviceId: summary.deviceId },
72906
- meta: {
72907
- summaryId: summary.id,
72908
- error: errMsg(err)
72909
- }
72910
- });
72911
- }
72912
- }
72913
72878
  /** Follows a split in the mirror, so the frame path scores each half on its
72914
72879
  * own members from the very next frame. */
72915
72880
  moveMirrorMembers(deviceId, fromId, toId, trackIds) {
@@ -75853,6 +75818,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75853
75818
  logger: logger.child("SensorEventStore")
75854
75819
  });
75855
75820
  this.summaryStore = new SummaryStore({
75821
+ trackIdsMatching: async (terms) => await this.trackStore?.trackIdsMatching(terms) ?? [],
75856
75822
  store: api.settingsStore,
75857
75823
  logger: this.ctx.logger.child("summary"),
75858
75824
  onEvict: async (ids) => {
@@ -75868,20 +75834,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
75868
75834
  for (const entry of this.stationaryRegistry?.list(deviceId) ?? []) if (entry.sourceTrackId !== void 0) ids.add(entry.sourceTrackId);
75869
75835
  return ids;
75870
75836
  },
75871
- labelsFor: async (trackIds) => {
75872
- const out = /* @__PURE__ */ new Map();
75873
- const tracks = await this.trackStore?.getPersistedByTrackIds(trackIds) ?? [];
75874
- for (const track of tracks) {
75875
- const label = track.label;
75876
- const subLabel = track.subLabel;
75877
- if (label === void 0 && subLabel === void 0) continue;
75878
- out.set(track.trackId, {
75879
- ...label !== void 0 ? { label } : {},
75880
- ...subLabel !== void 0 ? { subLabel } : {}
75881
- });
75882
- }
75883
- return out;
75884
- },
75885
75837
  now: () => Date.now(),
75886
75838
  logger: this.ctx.logger.child("summary-sealer"),
75887
75839
  frameSize: (deviceId) => {
@@ -1,4 +1,4 @@
1
- import { $ as TimelapseRuleSchema, $t as boolean, A as NcConditionDescriptorSchema, At as plateGalleryCapability, B as NcTaxonomySchema, Bt as videoclipsCapability, C as MOTION_CLOSE_AFTER_MS, Ct as isSourceCap, D as NC_DEFAULT_SNOOZE_MINUTES, E as NC_CONDITION_CATALOG, Et as notificationRulesCapability, F as NcScheduleSchema, Ft as sceneMonitorCapability, Gt as DeviceType, Ht as errMsg$1, I as NcSnoozeInputSchema, It as storageOccupancyCapability, J as SCENE_DIVERGED, Jt as isDeviceScopedCap, K as SCENE_DEFAULT_ANCHOR_THRESHOLD, Kt as createEvent, L as NcSnoozeSchema, Lt as subKindsOf, M as NcRulePatchSchema, Mt as readTimelapseGeneratedAt, N as NcRuleSchema, Nt as resolveLocationMode, Ot as pickClusterStepModels, P as NcRuleTargetSchema, Q as TimelapseRulePatchSchema, Qt as array, R as NcSnoozeSuppressedSchema, Rt as systemEventFilterApplies, St as isScheduleActive, T as NC_ALARM_SYSTEM_EVENT_KINDS, Tt as mayWriteToLocation, U as RECORDING_EXPORT_MAX_READ_BYTES, Ut as BaseAddon, V as OpsLogEntrySchema, Vt as zoneAnalyticsCapability, W as RetrainStatusSchema, Wt as CamProfileSchema, X as TIMELAPSE_DENSE_FLOOR_SEC, Xt as sleep$1, Y as SceneMonitorSchema, Yt as nodePin, Z as TimelapseRuleInputSchema, Zt as _enum, _ as FULL_IMAGE_BBOX, _t as failureContributionCapability, a as CLUSTER_MODEL_SCOPED_STEPS, an as record, at as audioMetricsCapability, b as MAX_ARCHIVED_DEBUG_NOTES, bt as isEventOwnerType, cn as EventCategory, ct as cosineSimilarity$1, dt as deriveRecordingMode, en as discriminatedUnion, et as TrackSourceSchema, f as DeclaredDevices, g as FIRST_LEVEL_MACRO_CLASSES, gt as faceGalleryCapability, h as EVENT_PAD_MS, ht as evictionPolicyOfLocation, i as BirthDecisionRecordSchema, in as partialRecord, it as assertTimelapseCadences, j as NcRuleInputSchema, jt as readDeviceStateFrom, k as NC_TAXONOMY, kt as pipelineAnalyticsCapability, lt as customAction, m as EVENT_OWNER_TYPES, mt as evaluateSensorEdge, n as ArchivedDebugNoteSchema, nn as number, nt as addonWidgetsSourceCapability, o as COCO_TO_MACRO, on as string, ot as audioModeOf, p as EVENT_KIND_BY_CAP, pt as encodeVectorBase64, q as SCENE_DEFAULT_UNCOVERED_POLICY, qt as hydrateSchema, r as BaseDevice, rn as object, rt as alarmPanelCapability, s as DEFAULT_EVENT_COLOR, sn as unknown, st as buildEventKindDescriptor, t as AUDIO_MACRO_LABELS, tn as literal, tt as VISIT_MERGE_GAP_MS, u as DETECTION_MACRO_CLASSES, ut as defineCustomActions, v as FailureCounters, w as MediaFileKindEnum, wt as kebabToCamel, x as MAX_BIRTH_DECISION_RECORDS, xt as isMediaOwnerType, y as LabelAttributionSchema, yt as isDetectionMacroClass, z as NcSystemEventKindSchema, zt as vectorDimFromBase64 } from "../dist-yyQ1H5yE.mjs";
1
+ import { $ as TimelapseRuleSchema, $t as boolean, A as NcConditionDescriptorSchema, At as plateGalleryCapability, B as NcTaxonomySchema, Bt as videoclipsCapability, C as MOTION_CLOSE_AFTER_MS, Ct as isSourceCap, D as NC_DEFAULT_SNOOZE_MINUTES, E as NC_CONDITION_CATALOG, Et as notificationRulesCapability, F as NcScheduleSchema, Ft as sceneMonitorCapability, Gt as DeviceType, Ht as errMsg$1, I as NcSnoozeInputSchema, It as storageOccupancyCapability, J as SCENE_DIVERGED, Jt as isDeviceScopedCap, K as SCENE_DEFAULT_ANCHOR_THRESHOLD, Kt as createEvent, L as NcSnoozeSchema, Lt as subKindsOf, M as NcRulePatchSchema, Mt as readTimelapseGeneratedAt, N as NcRuleSchema, Nt as resolveLocationMode, Ot as pickClusterStepModels, P as NcRuleTargetSchema, Q as TimelapseRulePatchSchema, Qt as array, R as NcSnoozeSuppressedSchema, Rt as systemEventFilterApplies, St as isScheduleActive, T as NC_ALARM_SYSTEM_EVENT_KINDS, Tt as mayWriteToLocation, U as RECORDING_EXPORT_MAX_READ_BYTES, Ut as BaseAddon, V as OpsLogEntrySchema, Vt as zoneAnalyticsCapability, W as RetrainStatusSchema, Wt as CamProfileSchema, X as TIMELAPSE_DENSE_FLOOR_SEC, Xt as sleep$1, Y as SceneMonitorSchema, Yt as nodePin, Z as TimelapseRuleInputSchema, Zt as _enum, _ as FULL_IMAGE_BBOX, _t as failureContributionCapability, a as CLUSTER_MODEL_SCOPED_STEPS, an as record, at as audioMetricsCapability, b as MAX_ARCHIVED_DEBUG_NOTES, bt as isEventOwnerType, cn as EventCategory, ct as cosineSimilarity$1, dt as deriveRecordingMode, en as discriminatedUnion, et as TrackSourceSchema, f as DeclaredDevices, g as FIRST_LEVEL_MACRO_CLASSES, gt as faceGalleryCapability, h as EVENT_PAD_MS, ht as evictionPolicyOfLocation, i as BirthDecisionRecordSchema, in as partialRecord, it as assertTimelapseCadences, j as NcRuleInputSchema, jt as readDeviceStateFrom, k as NC_TAXONOMY, kt as pipelineAnalyticsCapability, lt as customAction, m as EVENT_OWNER_TYPES, mt as evaluateSensorEdge, n as ArchivedDebugNoteSchema, nn as number, nt as addonWidgetsSourceCapability, o as COCO_TO_MACRO, on as string, ot as audioModeOf, p as EVENT_KIND_BY_CAP, pt as encodeVectorBase64, q as SCENE_DEFAULT_UNCOVERED_POLICY, qt as hydrateSchema, r as BaseDevice, rn as object, rt as alarmPanelCapability, s as DEFAULT_EVENT_COLOR, sn as unknown, st as buildEventKindDescriptor, t as AUDIO_MACRO_LABELS, tn as literal, tt as VISIT_MERGE_GAP_MS, u as DETECTION_MACRO_CLASSES, ut as defineCustomActions, v as FailureCounters, w as MediaFileKindEnum, wt as kebabToCamel, x as MAX_BIRTH_DECISION_RECORDS, xt as isMediaOwnerType, y as LabelAttributionSchema, yt as isDetectionMacroClass, z as NcSystemEventKindSchema, zt as vectorDimFromBase64 } from "../dist-Du1qDTXV.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";
@@ -43262,6 +43262,10 @@ function trackColumnsFor(projection, needsPositions = false) {
43262
43262
  * whatever order the engine felt like and a cursor that cannot resume.
43263
43263
  */
43264
43264
  var RECENT_IDS_COLUMNS = ["lastSeen"];
43265
+ /** How many track ids one filter pass may collect per tier. Bounded for the
43266
+ * same reason every bulk read here is: an unbounded scan of this table is
43267
+ * what D447 measured holding the settings thread for seconds. */
43268
+ var TRACK_MATCH_SCAN_LIMIT = 2e4;
43265
43269
  /** Spreadable `columns` for a store query — omitted entirely for `full`. */
43266
43270
  function projectionArg(projection, needsPositions = false) {
43267
43271
  const columns = trackColumnsFor(projection, needsPositions);
@@ -44349,6 +44353,42 @@ var TrackStore = class {
44349
44353
  * current subject out from under it. The orphan audit's grace window would
44350
44354
  * hide this for an hour and then stop hiding it.
44351
44355
  */
44356
+ /**
44357
+ * Track ids whose CLASS, sub-class or instance matches any of `terms`.
44358
+ *
44359
+ * The summaries' filter asks its question here rather than on a copy: a
44360
+ * label is a fact about the TRACK, it is decided while the track lives and
44361
+ * can still be re-decided afterwards (class voting, a late face match, an
44362
+ * operator enrolling a face and renaming that person). A copy stamped onto
44363
+ * the summary would answer with whatever was true at one instant and never
44364
+ * learn — which is the whole reason this lookup exists instead.
44365
+ *
44366
+ * Three queries because the filter compiler has no OR across fields, and one
44367
+ * term can be any of the three tiers: `animal` is a class, `dog` is a
44368
+ * sub-class, `Roberta` is an instance. Bounded, like every other bulk read
44369
+ * here.
44370
+ */
44371
+ async trackIdsMatching(terms) {
44372
+ const wanted = terms.filter((t) => t.length > 0);
44373
+ if (wanted.length === 0) return [];
44374
+ const ids = /* @__PURE__ */ new Set();
44375
+ for (const field of [
44376
+ "className",
44377
+ "label",
44378
+ "subLabel"
44379
+ ]) {
44380
+ const rows = await this.store.query.query({
44381
+ collection: TRACKS_COLLECTION,
44382
+ filter: {
44383
+ whereIn: { [field]: [...wanted] },
44384
+ limit: TRACK_MATCH_SCAN_LIMIT
44385
+ },
44386
+ columns: ["id"]
44387
+ });
44388
+ for (const row of rows) ids.add(row.id);
44389
+ }
44390
+ return [...ids];
44391
+ }
44352
44392
  async existingTrackIds(trackIds) {
44353
44393
  const PAGE = 500;
44354
44394
  const found = /* @__PURE__ */ new Set();
@@ -61402,30 +61442,7 @@ var SUMMARY_MEMBERS_COLUMNS = [
61402
61442
  {
61403
61443
  name: "className",
61404
61444
  type: "TEXT"
61405
- }),
61406
- (
61407
- /**
61408
- * The member's tier-1 label (`dog`, `van`) and tier-2 instance (a name, a
61409
- * plate), STAMPED AT SEAL.
61410
- *
61411
- * Not at the join, and the timing is the whole point: recognition lands
61412
- * during a track's life, almost always after it joined, so a label written at
61413
- * the join would be empty on exactly the members worth naming. At the seal
61414
- * every member's track has closed, so its label is final — there is nothing
61415
- * left to change it.
61416
- *
61417
- * They are COLUMNS because a filter has to be able to ask for them: "show me
61418
- * the arrivals with a dog" is a query over members, and answering it from the
61419
- * tracks would mean reading every track of every summary in the window.
61420
- */
61421
- {
61422
- name: "label",
61423
- type: "TEXT"
61424
- }),
61425
- {
61426
- name: "subLabel",
61427
- type: "TEXT"
61428
- }
61445
+ })
61429
61446
  ];
61430
61447
  var SUMMARY_MEMBERS_INDEXES = [
61431
61448
  {
@@ -61453,16 +61470,6 @@ var SUMMARY_MEMBERS_INDEXES = [
61453
61470
  function readRole(raw) {
61454
61471
  return raw === "primary" || raw === "sibling" || raw === "child" ? raw : null;
61455
61472
  }
61456
- /** The two stamped labels, present only when they hold something. An empty
61457
- * string is not a label and must never reach a filter as one. */
61458
- function labelFields(data) {
61459
- const label = data["label"];
61460
- const subLabel = data["subLabel"];
61461
- return {
61462
- ...typeof label === "string" && label.length > 0 ? { label } : {},
61463
- ...typeof subLabel === "string" && subLabel.length > 0 ? { subLabel } : {}
61464
- };
61465
- }
61466
61473
  function readClassName(raw) {
61467
61474
  return typeof raw === "string" && raw.length > 0 ? raw : null;
61468
61475
  }
@@ -61518,10 +61525,12 @@ var SummaryStore = class {
61518
61525
  store;
61519
61526
  logger;
61520
61527
  onEvict;
61528
+ trackIdsMatching;
61521
61529
  constructor(deps) {
61522
61530
  this.store = deps.store;
61523
61531
  this.logger = deps.logger;
61524
61532
  this.onEvict = deps.onEvict;
61533
+ this.trackIdsMatching = deps.trackIdsMatching;
61525
61534
  }
61526
61535
  static async declare(store) {
61527
61536
  await Promise.all([store.declareCollection.mutate({
@@ -61576,8 +61585,7 @@ var SummaryStore = class {
61576
61585
  deviceId: num$1(r.data["deviceId"]),
61577
61586
  joinedAt: num$1(r.data["timestamp"]),
61578
61587
  role: readRole(r.data["role"]),
61579
- className: readClassName(r.data["className"]),
61580
- ...labelFields(r.data)
61588
+ className: readClassName(r.data["className"])
61581
61589
  }));
61582
61590
  }
61583
61591
  /**
@@ -61765,23 +61773,6 @@ var SummaryStore = class {
61765
61773
  memberCount: leaving.length
61766
61774
  };
61767
61775
  }
61768
- /** Stamp a member's final labels. Best-effort and loud: a missing stamp
61769
- * costs a filter a match, never a membership. */
61770
- async stampMemberLabels(summaryIdValue, trackId, labels) {
61771
- try {
61772
- await this.store.update.mutate({
61773
- collection: SUMMARY_MEMBERS_COLLECTION$1,
61774
- id: memberRowId(summaryIdValue, trackId),
61775
- data: { ...labels }
61776
- });
61777
- } catch (err) {
61778
- this.logger.warn("summary: could not stamp a member label", { meta: {
61779
- summaryId: summaryIdValue,
61780
- trackId,
61781
- error: String(err)
61782
- } });
61783
- }
61784
- }
61785
61776
  /**
61786
61777
  * Put every OTHER member back to `sibling` after a new primary took the role.
61787
61778
  *
@@ -61987,29 +61978,36 @@ var SummaryStore = class {
61987
61978
  /**
61988
61979
  * The summaries whose MEMBERS match any of `classes`, or `null` for no filter.
61989
61980
  *
61990
- * Narrows first, on the members table, and only then reads summary rows.
61991
- * Filtering the other way round — a page of summaries, then their members —
61992
- * returns a short or empty page while matches sit further down the list, and
61993
- * the operator reads that as "there were none".
61981
+ * TRACKS first, then the members that name them, then the summaries. The
61982
+ * label is a fact about the track and it is asked of the track — a summary
61983
+ * that stored its own copy would answer with whatever was true when it
61984
+ * sealed, and would never learn that a face was enrolled and a person named
61985
+ * an hour later. The operator's rule: *a summary keeps the minimum
61986
+ * information necessary*, and a duplicated label is not the minimum, it is a
61987
+ * second truth with an expiry date.
61994
61988
  *
61995
- * Three queries because the filter compiler has no OR across fields, and one
61996
- * term can be any of the three tiers: `animal` is a class, `dog` is a
61997
- * sub-class, `Roberta` is an instance. The caller does not have to know
61998
- * which — that is the whole point of the operator's example.
61989
+ * Narrows before the page is cut. Filtering the other way round — a page of
61990
+ * summaries, then their members — returns a short or empty page while matches
61991
+ * sit further down the list, and the operator reads that as "there were none".
61999
61992
  */
62000
61993
  async summaryIdsMatching(classes) {
62001
61994
  const wanted = (classes ?? []).filter((t) => t.length > 0);
62002
61995
  if (wanted.length === 0) return null;
61996
+ const matchTracks = this.trackIdsMatching;
61997
+ if (matchTracks === void 0) {
61998
+ this.logger.warn("summary filter: no track matcher wired — refusing to answer unfiltered", { meta: { classes: wanted.length } });
61999
+ return /* @__PURE__ */ new Set();
62000
+ }
62001
+ const trackIds = await matchTracks(wanted);
62002
+ if (trackIds.length === 0) return /* @__PURE__ */ new Set();
62003
62003
  const ids = /* @__PURE__ */ new Set();
62004
- for (const field of [
62005
- "className",
62006
- "label",
62007
- "subLabel"
62008
- ]) {
62004
+ const PAGE = 500;
62005
+ for (let i = 0; i < trackIds.length; i += PAGE) {
62006
+ const chunk = trackIds.slice(i, i + PAGE);
62009
62007
  const rows = await this.store.query.query({
62010
62008
  collection: SUMMARY_MEMBERS_COLLECTION$1,
62011
62009
  filter: {
62012
- whereIn: { [field]: [...wanted] },
62010
+ whereIn: { trackId: [...chunk] },
62013
62011
  limit: MEMBER_MATCH_SCAN_LIMIT
62014
62012
  }
62015
62013
  });
@@ -62037,8 +62035,7 @@ var SummaryStore = class {
62037
62035
  deviceId: num$1(r.data["deviceId"]),
62038
62036
  joinedAt: num$1(r.data["timestamp"]),
62039
62037
  role: readRole(r.data["role"]),
62040
- className: readClassName(r.data["className"]),
62041
- ...labelFields(r.data)
62038
+ className: readClassName(r.data["className"])
62042
62039
  };
62043
62040
  const bucket = out.get(summaryIdValue);
62044
62041
  if (bucket === void 0) out.set(summaryIdValue, [member]);
@@ -72772,7 +72769,6 @@ var SummarySealer = class {
72772
72769
  }
72773
72770
  if (await this.isGroupFinished(summary, members) !== true) return;
72774
72771
  const sealedAt = this.deps.now();
72775
- await this.stampLabels(summary, members);
72776
72772
  try {
72777
72773
  if (await this.deps.summaries.seal(summary, sealedAt)) {
72778
72774
  const members = this.forgetGroup(deviceId, summaryId);
@@ -72805,37 +72801,6 @@ var SummarySealer = class {
72805
72801
  if (members === void 0) groups.set(summaryIdValue, new Set([trackId]));
72806
72802
  else members.add(trackId);
72807
72803
  }
72808
- /**
72809
- * Write each member's final label onto its row.
72810
- *
72811
- * At the seal every member's track has closed, so what the pipeline decided
72812
- * about it is final — this is the first and last moment the stamp can be
72813
- * both complete and correct.
72814
- */
72815
- async stampLabels(summary, members) {
72816
- const read = this.deps.labelsFor;
72817
- if (read === void 0 || members.length === 0) return;
72818
- try {
72819
- const labels = await read(members);
72820
- for (const [trackId, value] of labels) {
72821
- const label = value.label;
72822
- const subLabel = value.subLabel;
72823
- if ((label === void 0 || label.length === 0) && (subLabel === void 0 || subLabel.length === 0)) continue;
72824
- await this.deps.summaries.stampMemberLabels(summary.id, trackId, {
72825
- ...label !== void 0 && label.length > 0 ? { label } : {},
72826
- ...subLabel !== void 0 && subLabel.length > 0 ? { subLabel } : {}
72827
- });
72828
- }
72829
- } catch (err) {
72830
- this.deps.logger.warn("summary: could not stamp member labels — the seal stands", {
72831
- tags: { deviceId: summary.deviceId },
72832
- meta: {
72833
- summaryId: summary.id,
72834
- error: errMsg(err)
72835
- }
72836
- });
72837
- }
72838
- }
72839
72804
  /** Follows a split in the mirror, so the frame path scores each half on its
72840
72805
  * own members from the very next frame. */
72841
72806
  moveMirrorMembers(deviceId, fromId, toId, trackIds) {
@@ -75779,6 +75744,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
75779
75744
  logger: logger.child("SensorEventStore")
75780
75745
  });
75781
75746
  this.summaryStore = new SummaryStore({
75747
+ trackIdsMatching: async (terms) => await this.trackStore?.trackIdsMatching(terms) ?? [],
75782
75748
  store: api.settingsStore,
75783
75749
  logger: this.ctx.logger.child("summary"),
75784
75750
  onEvict: async (ids) => {
@@ -75794,20 +75760,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
75794
75760
  for (const entry of this.stationaryRegistry?.list(deviceId) ?? []) if (entry.sourceTrackId !== void 0) ids.add(entry.sourceTrackId);
75795
75761
  return ids;
75796
75762
  },
75797
- labelsFor: async (trackIds) => {
75798
- const out = /* @__PURE__ */ new Map();
75799
- const tracks = await this.trackStore?.getPersistedByTrackIds(trackIds) ?? [];
75800
- for (const track of tracks) {
75801
- const label = track.label;
75802
- const subLabel = track.subLabel;
75803
- if (label === void 0 && subLabel === void 0) continue;
75804
- out.set(track.trackId, {
75805
- ...label !== void 0 ? { label } : {},
75806
- ...subLabel !== void 0 ? { subLabel } : {}
75807
- });
75808
- }
75809
- return out;
75810
- },
75811
75763
  now: () => Date.now(),
75812
75764
  logger: this.ctx.logger.child("summary-sealer"),
75813
75765
  frameSize: (deviceId) => {
@@ -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-w7xIpvHc.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D-bBcfRY.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.232",
3
+ "version": "1.2.234",
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",