@camstack/addon-post-analysis 1.2.202 → 1.2.203

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.
@@ -12525,6 +12525,9 @@ var QueryFilterSchema = object({
12525
12525
  whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
12526
12526
  /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
12527
12527
  whereNot: record(string(), unknown()).optional(),
12528
+ /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
12529
+ * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
12530
+ whereNotIn: record(string(), array(unknown())).optional(),
12528
12531
  orderBy: object({
12529
12532
  field: string(),
12530
12533
  direction: _enum(["asc", "desc"])
@@ -12545,7 +12548,8 @@ var MutationFilterSchema = object({
12545
12548
  where: record(string(), unknown()).optional(),
12546
12549
  whereIn: record(string(), array(unknown())).optional(),
12547
12550
  whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
12548
- whereNot: record(string(), unknown()).optional()
12551
+ whereNot: record(string(), unknown()).optional(),
12552
+ whereNotIn: record(string(), array(unknown())).optional()
12549
12553
  });
12550
12554
  /**
12551
12555
  * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
@@ -19884,10 +19888,12 @@ var TrackSourceSchema = _enum([
19884
19888
  * Terminal for the plain `markForTrain` toggle: returning it to `staging` is
19885
19889
  * a deliberate action of the retrain page, not a side effect of a checkbox.
19886
19890
  *
19887
- * There is no `null`. The state is stored `TEXT NOT NULL DEFAULT 'none'` because
19888
- * the store's filter language has only positive equality and `whereIn` no
19889
- * negation, no IS NULL so a NULL would be unselectable by ANY predicate and
19890
- * would make the entire pre-column history immortal in one deploy.
19891
+ * There is no `null`. The state is stored `TEXT NOT NULL DEFAULT 'none'` so that
19892
+ * every row is selectable by a positive predicate. (The filter language has
19893
+ * since grown the NULL-safe `whereNot` / `whereNotIn` and an `IS NULL` reading
19894
+ * of `where: { f: null }`, which is how {@link TrackSourceSchema} can be
19895
+ * filtered on a NULLABLE column — see `excludeSources`. It did not when this
19896
+ * column was designed, and a NOT NULL column is still the better shape.)
19891
19897
  */
19892
19898
  var RetrainStatusSchema = _enum([
19893
19899
  "none",
@@ -20623,7 +20629,9 @@ var RecentTracksQueryInput = object({
20623
20629
  * whose class list is unreadable are kept, and the client's rule stays the
20624
20630
  * exact one.
20625
20631
  */
20626
- classes: array(string()).optional()
20632
+ classes: array(string()).optional(),
20633
+ /** See {@link ExcludeSourcesDoc}. */
20634
+ excludeSources: array(TrackSourceSchema).optional()
20627
20635
  });
20628
20636
  /**
20629
20637
  * A Summary (D359): the per-camera session envelope that references tracks.
@@ -21065,7 +21073,9 @@ var pipelineAnalyticsCapability = {
21065
21073
  * selected is an operator who has not narrowed anything, and an empty
21066
21074
  * timeline would read as a camera that saw nothing.
21067
21075
  */
21068
- classes: array(string()).optional()
21076
+ classes: array(string()).optional(),
21077
+ /** See {@link ExcludeSourcesDoc}. */
21078
+ excludeSources: array(TrackSourceSchema).optional()
21069
21079
  }), array(TrackSchema).readonly()),
21070
21080
  /**
21071
21081
  * Batched cluster-wide track listing — ONE call for the events page /
@@ -12556,6 +12556,9 @@ var QueryFilterSchema = object({
12556
12556
  whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
12557
12557
  /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
12558
12558
  whereNot: record(string(), unknown()).optional(),
12559
+ /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
12560
+ * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
12561
+ whereNotIn: record(string(), array(unknown())).optional(),
12559
12562
  orderBy: object({
12560
12563
  field: string(),
12561
12564
  direction: _enum(["asc", "desc"])
@@ -12576,7 +12579,8 @@ var MutationFilterSchema = object({
12576
12579
  where: record(string(), unknown()).optional(),
12577
12580
  whereIn: record(string(), array(unknown())).optional(),
12578
12581
  whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
12579
- whereNot: record(string(), unknown()).optional()
12582
+ whereNot: record(string(), unknown()).optional(),
12583
+ whereNotIn: record(string(), array(unknown())).optional()
12580
12584
  });
12581
12585
  /**
12582
12586
  * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
@@ -19915,10 +19919,12 @@ var TrackSourceSchema = _enum([
19915
19919
  * Terminal for the plain `markForTrain` toggle: returning it to `staging` is
19916
19920
  * a deliberate action of the retrain page, not a side effect of a checkbox.
19917
19921
  *
19918
- * There is no `null`. The state is stored `TEXT NOT NULL DEFAULT 'none'` because
19919
- * the store's filter language has only positive equality and `whereIn` no
19920
- * negation, no IS NULL so a NULL would be unselectable by ANY predicate and
19921
- * would make the entire pre-column history immortal in one deploy.
19922
+ * There is no `null`. The state is stored `TEXT NOT NULL DEFAULT 'none'` so that
19923
+ * every row is selectable by a positive predicate. (The filter language has
19924
+ * since grown the NULL-safe `whereNot` / `whereNotIn` and an `IS NULL` reading
19925
+ * of `where: { f: null }`, which is how {@link TrackSourceSchema} can be
19926
+ * filtered on a NULLABLE column — see `excludeSources`. It did not when this
19927
+ * column was designed, and a NOT NULL column is still the better shape.)
19922
19928
  */
19923
19929
  var RetrainStatusSchema = _enum([
19924
19930
  "none",
@@ -20654,7 +20660,9 @@ var RecentTracksQueryInput = object({
20654
20660
  * whose class list is unreadable are kept, and the client's rule stays the
20655
20661
  * exact one.
20656
20662
  */
20657
- classes: array(string()).optional()
20663
+ classes: array(string()).optional(),
20664
+ /** See {@link ExcludeSourcesDoc}. */
20665
+ excludeSources: array(TrackSourceSchema).optional()
20658
20666
  });
20659
20667
  /**
20660
20668
  * A Summary (D359): the per-camera session envelope that references tracks.
@@ -21096,7 +21104,9 @@ var pipelineAnalyticsCapability = {
21096
21104
  * selected is an operator who has not narrowed anything, and an empty
21097
21105
  * timeline would read as a camera that saw nothing.
21098
21106
  */
21099
- classes: array(string()).optional()
21107
+ classes: array(string()).optional(),
21108
+ /** See {@link ExcludeSourcesDoc}. */
21109
+ excludeSources: array(TrackSourceSchema).optional()
21100
21110
  }), array(TrackSchema).readonly()),
21101
21111
  /**
21102
21112
  * Batched cluster-wide track listing — ONE call for the events page /
@@ -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-DbgfbjZC.js");
5
+ const require_dist = require("../dist-rqvrMbFv.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 { F as PoolMemoryWatchdog, Pt as BaseAddon, Tt as resolvePoolMemoryPolicy, at as embeddingEncoderCapability, dt as hfModelUrl, vt as parseProcStatus } from "../dist-1xB8qj2h.mjs";
1
+ import { F as PoolMemoryWatchdog, Pt as BaseAddon, Tt as resolvePoolMemoryPolicy, at as embeddingEncoderCapability, dt as hfModelUrl, vt as parseProcStatus } from "../dist-B2bqI0T1.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.84",
6
+ version: "1.2.85",
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.164",
21
+ version: "1.2.165",
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.132",
36
+ version: "1.2.133",
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.164",
39
+ version: "1.2.165",
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.84",
48
+ version: "1.2.85",
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.132",
84
+ version: "1.2.133",
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-DbgfbjZC.js");
5
+ const require_dist = require("../dist-rqvrMbFv.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");
@@ -39653,6 +39653,58 @@ function isShortMotionlessTrack(lifeMs, movedPx, className) {
39653
39653
  return lifeMs <= 25e3 && movedPx <= motionlessMaxPxForClass(className);
39654
39654
  }
39655
39655
  //#endregion
39656
+ //#region src/pipeline-analytics/store/track-source-filter.ts
39657
+ /**
39658
+ * The SQL predicate behind `excludeSources` — track provenance as a real query
39659
+ * filter (see `ExcludeSourcesDoc` in the cap for why the query needs one).
39660
+ *
39661
+ * ## The column is nullable, and that decides everything
39662
+ *
39663
+ * `persistCompleted` writes `source` only when the track carries one, and a
39664
+ * pipeline track does not: on the live hub every pipeline row's `source` is
39665
+ * NULL and only the synthetic rows (`audio`, `sensor`) carry a value. So there
39666
+ * are two exact compilations and the choice between them is purely "is
39667
+ * `pipeline` one of the sources being excluded":
39668
+ *
39669
+ * - **`pipeline` kept** → NULL-safe `whereNotIn`. `(source IS NULL OR source
39670
+ * NOT IN (…))` keeps every pipeline row without naming a value they do not
39671
+ * carry, and keeps any source added after this shipped — an exclusion list
39672
+ * can only hide what it names.
39673
+ * - **`pipeline` excluded** → positive `whereIn` over what survives. NULL is
39674
+ * then exactly the set being dropped, and a NULL-safe exclusion would keep
39675
+ * precisely the rows it was asked to remove. (Excluding EVERY known source
39676
+ * is the degenerate case: `whereIn` with an empty list, which the compiler
39677
+ * reads as "no filter", so it is answered here instead — see below.)
39678
+ *
39679
+ * Both are exact. Neither leaves a row's fate to a default, which is the whole
39680
+ * point: the class filter this replaces was a SUPERSET that kept every row it
39681
+ * could not read, and that is what made "Sensor" and "Audio" select the same
39682
+ * 116 rows on the live hub.
39683
+ */
39684
+ /** Every source the build knows. Derived from the schema, never restated — a
39685
+ * source added to the enum must not need a second edit here to be filterable. */
39686
+ var KNOWN_SOURCES = require_dist.TrackSourceSchema.options;
39687
+ /**
39688
+ * Compile `excludeSources` into a store filter fragment.
39689
+ *
39690
+ * Pure. Unknown / duplicate entries are normalised away: the input crosses a
39691
+ * wire from a client whose enum may be older or newer than this one.
39692
+ */
39693
+ function trackSourceFilter(excludeSources) {
39694
+ const excluded = new Set((excludeSources ?? []).filter((s) => KNOWN_SOURCES.includes(s)));
39695
+ if (excluded.size === 0) return { kind: "none" };
39696
+ const kept = KNOWN_SOURCES.filter((s) => !excluded.has(s));
39697
+ if (kept.length === 0) return { kind: "impossible" };
39698
+ if (!excluded.has("pipeline")) return {
39699
+ kind: "filter",
39700
+ filter: { whereNotIn: { source: [...excluded] } }
39701
+ };
39702
+ return {
39703
+ kind: "filter",
39704
+ filter: { whereIn: { source: kept } }
39705
+ };
39706
+ }
39707
+ //#endregion
39656
39708
  //#region src/pipeline-analytics/store/track-store.ts
39657
39709
  /**
39658
39710
  * WHY a track's row was written EARLY — i.e. by {@link TrackStore.closeActive}
@@ -41222,6 +41274,9 @@ var TrackStore = class {
41222
41274
  const timeBetween = params.since !== void 0 || params.until !== void 0 ? { firstSeen: [params.since ?? 0, params.until ?? Date.now()] } : {};
41223
41275
  const stationaryExclusion = params.excludeStationaryPromotions === true ? { whereNot: { closedReason: "stationary-promotion" } } : {};
41224
41276
  const classFilter = params.classes !== void 0 && params.classes.length > 0 ? { whereJsonArrayAny: { classes: [...params.classes] } } : {};
41277
+ const source = trackSourceFilter(params.excludeSources);
41278
+ if (source.kind === "impossible") return [];
41279
+ const sourceFilter = source.kind === "filter" ? source.filter : {};
41225
41280
  if (params.zone === void 0) return (await this.store.query.query({
41226
41281
  collection: TRACKS_COLLECTION,
41227
41282
  filter: {
@@ -41229,6 +41284,7 @@ var TrackStore = class {
41229
41284
  ...Object.keys(timeBetween).length > 0 ? { whereBetween: timeBetween } : {},
41230
41285
  ...stationaryExclusion,
41231
41286
  ...classFilter,
41287
+ ...sourceFilter,
41232
41288
  orderBy: {
41233
41289
  field: "firstSeen",
41234
41290
  direction: "desc"
@@ -41245,6 +41301,7 @@ var TrackStore = class {
41245
41301
  where: { deviceId: params.deviceId },
41246
41302
  ...stationaryExclusion,
41247
41303
  ...classFilter,
41304
+ ...sourceFilter,
41248
41305
  whereBetween: {
41249
41306
  ...timeBetween,
41250
41307
  envMinX: [-1e6, bounds.maxX],
@@ -41266,6 +41323,7 @@ var TrackStore = class {
41266
41323
  where: { deviceId: params.deviceId },
41267
41324
  ...stationaryExclusion,
41268
41325
  ...classFilter,
41326
+ ...sourceFilter,
41269
41327
  ...Object.keys(timeBetween).length > 0 ? { whereBetween: timeBetween } : {},
41270
41328
  orderBy: {
41271
41329
  field: "firstSeen",
@@ -41363,6 +41421,12 @@ var TrackStore = class {
41363
41421
  page: [],
41364
41422
  nextCursor: null
41365
41423
  };
41424
+ const source = trackSourceFilter(params.excludeSources);
41425
+ if (source.kind === "impossible") return {
41426
+ page: [],
41427
+ nextCursor: null
41428
+ };
41429
+ const sourceFilter = source.kind === "filter" ? source.filter : {};
41366
41430
  const requested = Math.min(Math.max(params.limit ?? RECENT_DEFAULT_LIMIT, 1), RECENT_MAX_LIMIT);
41367
41431
  /**
41368
41432
  * The bound is on `deviceCount x limit`, because that — not `limit` — is
@@ -41395,6 +41459,7 @@ var TrackStore = class {
41395
41459
  where: { deviceId },
41396
41460
  ...stationaryExclusion,
41397
41461
  ...classFilter,
41462
+ ...sourceFilter,
41398
41463
  whereBetween: { lastSeen: range },
41399
41464
  orderBy: {
41400
41465
  field: "lastSeen",
@@ -63602,6 +63667,27 @@ var SyntheticTrackMaterializer = class {
63602
63667
  trackId,
63603
63668
  deviceId: input.cameraId,
63604
63669
  className: input.kind,
63670
+ /**
63671
+ * The accumulated-class column, seeded with the one class this row will
63672
+ * ever have. A pipeline track gets `classes: [className]` on its first
63673
+ * observation; a synthetic row was left with the column NULL, and
63674
+ * `whereJsonArrayAny` — the operator `classes` compiles to — is a
63675
+ * deliberate SUPERSET that KEEPS a row whose column it cannot read.
63676
+ *
63677
+ * So every class selection returned every synthetic row and no class
63678
+ * selection ever SELECTED one: measured on the live hub, `classes:
63679
+ * ['person']` came back with 66 contact markers and 29 audio markers
63680
+ * attached, `classes: ['contact']` returned no contact row by its class
63681
+ * (only the 116 rows the escape hatch keeps), and a class that exists
63682
+ * nowhere answered those same 116. That is the "the sensor and control
63683
+ * filters do nothing" report, exactly.
63684
+ *
63685
+ * Writing it here makes a synthetic row selectable BY ITS KIND like any
63686
+ * other. Rows already on disk keep a NULL column and stay in the
63687
+ * superset — which is what `excludeSources` is for: provenance is not a
63688
+ * class, and the source filter is exact for the whole history.
63689
+ */
63690
+ classes: [input.kind],
63605
63691
  source: input.source,
63606
63692
  ...input.producingDeviceName !== void 0 ? { producingDeviceName: input.producingDeviceName } : {},
63607
63693
  ...input.audioLabels !== void 0 && input.audioLabels.length > 0 ? { audioLabels: input.audioLabels } : {},
@@ -1,4 +1,4 @@
1
- import { $ as audioModeOf, $t as EventCategory, A as NcSnoozeSchema, At as vectorDimFromBase64, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as nodePin, C as NcConditionDescriptorSchema, Ct as readTimelapseGeneratedAt, D as NcRuleTargetSchema, Dt as storageOccupancyCapability, E as NcRuleSchema, Et as sceneMonitorCapability, Ft as CamProfileSchema, G as TimelapseRulePatchSchema, Gt as discriminatedUnion, H as SceneMonitorSchema, Ht as _enum, I as RECORDING_EXPORT_MAX_READ_BYTES, It as DeviceType, J as VISIT_MERGE_GAP_MS, Jt as object, K as TimelapseRuleSchema, Kt as literal, L as RetrainStatusSchema, Lt as createEvent, M as NcSystemEventKindSchema, Mt as zoneAnalyticsCapability, N as NcTaxonomySchema, Nt as errMsg$1, O as NcScheduleSchema, Ot as subKindsOf, P as OpsLogEntrySchema, Pt as BaseAddon, Q as audioMetricsCapability, Qt as unknown, Rt as hydrateSchema, S as NC_TAXONOMY, St as readDeviceStateFrom, T as NcRulePatchSchema, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as array, V as SCENE_DIVERGED, Vt as sleep$1, W as TimelapseRuleInputSchema, Wt as boolean, X as alarmPanelCapability, Xt as record, Y as addonWidgetsSourceCapability, Yt as partialRecord, Z as assertTimelapseCadences, Zt as string, _ as MediaFileKindEnum, _t as notificationRulesCapability, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as pipelineAnalyticsCapability, c as DETECTION_MACRO_CLASSES, ct as evictionPolicyOfLocation, d as EVENT_KIND_BY_CAP, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isDetectionMacroClass, g as MACRO_LABELS, gt as mayWriteToLocation, h as LabelAttributionSchema, ht as kebabToCamel, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as videoclipsCapability, k as NcSnoozeInputSchema, kt as systemEventFilterApplies, lt as faceGalleryCapability, m as FailureCounters, mt as isSourceCap, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isScheduleActive, q as TrackSourceSchema, qt as number, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, ut as failureContributionCapability, v as NC_ALARM_SYSTEM_EVENT_KINDS, w as NcRuleInputSchema, wt as resolveLocationMode, xt as plateGalleryCapability, y as NC_CONDITION_CATALOG, yt as pickClusterStepModels, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as isDeviceScopedCap } from "../dist-1xB8qj2h.mjs";
1
+ import { $ as audioModeOf, $t as EventCategory, A as NcSnoozeSchema, At as vectorDimFromBase64, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as nodePin, C as NcConditionDescriptorSchema, Ct as readTimelapseGeneratedAt, D as NcRuleTargetSchema, Dt as storageOccupancyCapability, E as NcRuleSchema, Et as sceneMonitorCapability, Ft as CamProfileSchema, G as TimelapseRulePatchSchema, Gt as discriminatedUnion, H as SceneMonitorSchema, Ht as _enum, I as RECORDING_EXPORT_MAX_READ_BYTES, It as DeviceType, J as VISIT_MERGE_GAP_MS, Jt as object, K as TimelapseRuleSchema, Kt as literal, L as RetrainStatusSchema, Lt as createEvent, M as NcSystemEventKindSchema, Mt as zoneAnalyticsCapability, N as NcTaxonomySchema, Nt as errMsg$1, O as NcScheduleSchema, Ot as subKindsOf, P as OpsLogEntrySchema, Pt as BaseAddon, Q as audioMetricsCapability, Qt as unknown, Rt as hydrateSchema, S as NC_TAXONOMY, St as readDeviceStateFrom, T as NcRulePatchSchema, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as array, V as SCENE_DIVERGED, Vt as sleep$1, W as TimelapseRuleInputSchema, Wt as boolean, X as alarmPanelCapability, Xt as record, Y as addonWidgetsSourceCapability, Yt as partialRecord, Z as assertTimelapseCadences, Zt as string, _ as MediaFileKindEnum, _t as notificationRulesCapability, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as pipelineAnalyticsCapability, c as DETECTION_MACRO_CLASSES, ct as evictionPolicyOfLocation, d as EVENT_KIND_BY_CAP, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isDetectionMacroClass, g as MACRO_LABELS, gt as mayWriteToLocation, h as LabelAttributionSchema, ht as kebabToCamel, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as videoclipsCapability, k as NcSnoozeInputSchema, kt as systemEventFilterApplies, lt as faceGalleryCapability, m as FailureCounters, mt as isSourceCap, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isScheduleActive, q as TrackSourceSchema, qt as number, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, ut as failureContributionCapability, v as NC_ALARM_SYSTEM_EVENT_KINDS, w as NcRuleInputSchema, wt as resolveLocationMode, xt as plateGalleryCapability, y as NC_CONDITION_CATALOG, yt as pickClusterStepModels, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as isDeviceScopedCap } from "../dist-B2bqI0T1.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";
@@ -39628,6 +39628,58 @@ function isShortMotionlessTrack(lifeMs, movedPx, className) {
39628
39628
  return lifeMs <= 25e3 && movedPx <= motionlessMaxPxForClass(className);
39629
39629
  }
39630
39630
  //#endregion
39631
+ //#region src/pipeline-analytics/store/track-source-filter.ts
39632
+ /**
39633
+ * The SQL predicate behind `excludeSources` — track provenance as a real query
39634
+ * filter (see `ExcludeSourcesDoc` in the cap for why the query needs one).
39635
+ *
39636
+ * ## The column is nullable, and that decides everything
39637
+ *
39638
+ * `persistCompleted` writes `source` only when the track carries one, and a
39639
+ * pipeline track does not: on the live hub every pipeline row's `source` is
39640
+ * NULL and only the synthetic rows (`audio`, `sensor`) carry a value. So there
39641
+ * are two exact compilations and the choice between them is purely "is
39642
+ * `pipeline` one of the sources being excluded":
39643
+ *
39644
+ * - **`pipeline` kept** → NULL-safe `whereNotIn`. `(source IS NULL OR source
39645
+ * NOT IN (…))` keeps every pipeline row without naming a value they do not
39646
+ * carry, and keeps any source added after this shipped — an exclusion list
39647
+ * can only hide what it names.
39648
+ * - **`pipeline` excluded** → positive `whereIn` over what survives. NULL is
39649
+ * then exactly the set being dropped, and a NULL-safe exclusion would keep
39650
+ * precisely the rows it was asked to remove. (Excluding EVERY known source
39651
+ * is the degenerate case: `whereIn` with an empty list, which the compiler
39652
+ * reads as "no filter", so it is answered here instead — see below.)
39653
+ *
39654
+ * Both are exact. Neither leaves a row's fate to a default, which is the whole
39655
+ * point: the class filter this replaces was a SUPERSET that kept every row it
39656
+ * could not read, and that is what made "Sensor" and "Audio" select the same
39657
+ * 116 rows on the live hub.
39658
+ */
39659
+ /** Every source the build knows. Derived from the schema, never restated — a
39660
+ * source added to the enum must not need a second edit here to be filterable. */
39661
+ var KNOWN_SOURCES = TrackSourceSchema.options;
39662
+ /**
39663
+ * Compile `excludeSources` into a store filter fragment.
39664
+ *
39665
+ * Pure. Unknown / duplicate entries are normalised away: the input crosses a
39666
+ * wire from a client whose enum may be older or newer than this one.
39667
+ */
39668
+ function trackSourceFilter(excludeSources) {
39669
+ const excluded = new Set((excludeSources ?? []).filter((s) => KNOWN_SOURCES.includes(s)));
39670
+ if (excluded.size === 0) return { kind: "none" };
39671
+ const kept = KNOWN_SOURCES.filter((s) => !excluded.has(s));
39672
+ if (kept.length === 0) return { kind: "impossible" };
39673
+ if (!excluded.has("pipeline")) return {
39674
+ kind: "filter",
39675
+ filter: { whereNotIn: { source: [...excluded] } }
39676
+ };
39677
+ return {
39678
+ kind: "filter",
39679
+ filter: { whereIn: { source: kept } }
39680
+ };
39681
+ }
39682
+ //#endregion
39631
39683
  //#region src/pipeline-analytics/store/track-store.ts
39632
39684
  /**
39633
39685
  * WHY a track's row was written EARLY — i.e. by {@link TrackStore.closeActive}
@@ -41197,6 +41249,9 @@ var TrackStore = class {
41197
41249
  const timeBetween = params.since !== void 0 || params.until !== void 0 ? { firstSeen: [params.since ?? 0, params.until ?? Date.now()] } : {};
41198
41250
  const stationaryExclusion = params.excludeStationaryPromotions === true ? { whereNot: { closedReason: "stationary-promotion" } } : {};
41199
41251
  const classFilter = params.classes !== void 0 && params.classes.length > 0 ? { whereJsonArrayAny: { classes: [...params.classes] } } : {};
41252
+ const source = trackSourceFilter(params.excludeSources);
41253
+ if (source.kind === "impossible") return [];
41254
+ const sourceFilter = source.kind === "filter" ? source.filter : {};
41200
41255
  if (params.zone === void 0) return (await this.store.query.query({
41201
41256
  collection: TRACKS_COLLECTION,
41202
41257
  filter: {
@@ -41204,6 +41259,7 @@ var TrackStore = class {
41204
41259
  ...Object.keys(timeBetween).length > 0 ? { whereBetween: timeBetween } : {},
41205
41260
  ...stationaryExclusion,
41206
41261
  ...classFilter,
41262
+ ...sourceFilter,
41207
41263
  orderBy: {
41208
41264
  field: "firstSeen",
41209
41265
  direction: "desc"
@@ -41220,6 +41276,7 @@ var TrackStore = class {
41220
41276
  where: { deviceId: params.deviceId },
41221
41277
  ...stationaryExclusion,
41222
41278
  ...classFilter,
41279
+ ...sourceFilter,
41223
41280
  whereBetween: {
41224
41281
  ...timeBetween,
41225
41282
  envMinX: [-1e6, bounds.maxX],
@@ -41241,6 +41298,7 @@ var TrackStore = class {
41241
41298
  where: { deviceId: params.deviceId },
41242
41299
  ...stationaryExclusion,
41243
41300
  ...classFilter,
41301
+ ...sourceFilter,
41244
41302
  ...Object.keys(timeBetween).length > 0 ? { whereBetween: timeBetween } : {},
41245
41303
  orderBy: {
41246
41304
  field: "firstSeen",
@@ -41338,6 +41396,12 @@ var TrackStore = class {
41338
41396
  page: [],
41339
41397
  nextCursor: null
41340
41398
  };
41399
+ const source = trackSourceFilter(params.excludeSources);
41400
+ if (source.kind === "impossible") return {
41401
+ page: [],
41402
+ nextCursor: null
41403
+ };
41404
+ const sourceFilter = source.kind === "filter" ? source.filter : {};
41341
41405
  const requested = Math.min(Math.max(params.limit ?? RECENT_DEFAULT_LIMIT, 1), RECENT_MAX_LIMIT);
41342
41406
  /**
41343
41407
  * The bound is on `deviceCount x limit`, because that — not `limit` — is
@@ -41370,6 +41434,7 @@ var TrackStore = class {
41370
41434
  where: { deviceId },
41371
41435
  ...stationaryExclusion,
41372
41436
  ...classFilter,
41437
+ ...sourceFilter,
41373
41438
  whereBetween: { lastSeen: range },
41374
41439
  orderBy: {
41375
41440
  field: "lastSeen",
@@ -63528,6 +63593,27 @@ var SyntheticTrackMaterializer = class {
63528
63593
  trackId,
63529
63594
  deviceId: input.cameraId,
63530
63595
  className: input.kind,
63596
+ /**
63597
+ * The accumulated-class column, seeded with the one class this row will
63598
+ * ever have. A pipeline track gets `classes: [className]` on its first
63599
+ * observation; a synthetic row was left with the column NULL, and
63600
+ * `whereJsonArrayAny` — the operator `classes` compiles to — is a
63601
+ * deliberate SUPERSET that KEEPS a row whose column it cannot read.
63602
+ *
63603
+ * So every class selection returned every synthetic row and no class
63604
+ * selection ever SELECTED one: measured on the live hub, `classes:
63605
+ * ['person']` came back with 66 contact markers and 29 audio markers
63606
+ * attached, `classes: ['contact']` returned no contact row by its class
63607
+ * (only the 116 rows the escape hatch keeps), and a class that exists
63608
+ * nowhere answered those same 116. That is the "the sensor and control
63609
+ * filters do nothing" report, exactly.
63610
+ *
63611
+ * Writing it here makes a synthetic row selectable BY ITS KIND like any
63612
+ * other. Rows already on disk keep a NULL column and stay in the
63613
+ * superset — which is what `excludeSources` is for: provenance is not a
63614
+ * class, and the source filter is exact for the whole history.
63615
+ */
63616
+ classes: [input.kind],
63531
63617
  source: input.source,
63532
63618
  ...input.producingDeviceName !== void 0 ? { producingDeviceName: input.producingDeviceName } : {},
63533
63619
  ...input.audioLabels !== void 0 && input.audioLabels.length > 0 ? { audioLabels: input.audioLabels } : {},
@@ -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-DwFi68p5.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BhGMdGiF.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.202",
3
+ "version": "1.2.203",
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",