@camstack/addon-post-analysis 1.2.198 → 1.2.199

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.
@@ -12814,6 +12814,23 @@ function recordToRow$2(subject, data) {
12814
12814
  };
12815
12815
  }
12816
12816
  //#endregion
12817
+ //#region src/notification-center/no-match-report-key.ts
12818
+ /**
12819
+ * Stand-in for "this subject named no class". A distinct token rather than an
12820
+ * empty string so a class-less subject occupies its own bucket instead of
12821
+ * sharing one with every other class-less kind by accident.
12822
+ */
12823
+ var NO_CLASSES = "∅";
12824
+ /**
12825
+ * The bucket key. Stable across calls, insensitive to class ORDER, DUPLICATES
12826
+ * and CASE — a detector that re-orders its class list, repeats one, or changes
12827
+ * its capitalisation must not fork the bucket and double the log volume.
12828
+ */
12829
+ function noMatchReportKey(subject) {
12830
+ const classes = [...new Set(subject.classNames.map((c) => c.toLowerCase()))].sort();
12831
+ return `${subject.deviceId}|${subject.kind}|${classes.length > 0 ? classes.join(",") : NO_CLASSES}`;
12832
+ }
12833
+ //#endregion
12817
12834
  //#region src/notification-center/occupancy-watcher.ts
12818
12835
  /** Sentinel key segments for the "no zone" (whole-frame) and "no class" scopes. */
12819
12836
  var FRAME_SCOPE = "@frame";
@@ -21945,10 +21962,15 @@ var NotificationCenter = class NotificationCenter {
21945
21962
  * which leaves those notifications text-only rather than broken.
21946
21963
  */
21947
21964
  stills;
21948
- /** Per-device rate limit for the "matched NO rule" report — see `reportNoMatch`. */
21965
+ /**
21966
+ * Rate limit for the "matched NO rule" report — see `reportNoMatch`. Keyed by
21967
+ * {@link noMatchReportKey} (device + subject kind + class set), NOT by device:
21968
+ * a person's verdict must never consume the window a cat's verdict needed on
21969
+ * the same camera ([D390]).
21970
+ */
21949
21971
  lastNoMatchReportAt = /* @__PURE__ */ new Map();
21950
21972
  noMatchSuppressed = /* @__PURE__ */ new Map();
21951
- /** Same rate-limit pair for the per-camera mute drop — see `reportMutedDrop`. */
21973
+ /** Same rate-limit pair, same key, for the per-camera mute drop — see `reportMutedDrop`. */
21952
21974
  lastMutedReportAt = /* @__PURE__ */ new Map();
21953
21975
  mutedSuppressed = /* @__PURE__ */ new Map();
21954
21976
  /**
@@ -24719,14 +24741,19 @@ var NotificationCenter = class NotificationCenter {
24719
24741
  * is identical: "why did this camera not notify me?".
24720
24742
  */
24721
24743
  reportMutedDrop(subject, kind, now) {
24722
- const last = this.lastMutedReportAt.get(subject.deviceId) ?? 0;
24723
- const suppressed = this.mutedSuppressed.get(subject.deviceId) ?? 0;
24744
+ const bucket = noMatchReportKey({
24745
+ deviceId: subject.deviceId,
24746
+ kind,
24747
+ classNames: subject.classNames
24748
+ });
24749
+ const last = this.lastMutedReportAt.get(bucket) ?? 0;
24750
+ const suppressed = this.mutedSuppressed.get(bucket) ?? 0;
24724
24751
  if (now - last < NO_MATCH_REPORT_INTERVAL_MS) {
24725
- this.mutedSuppressed.set(subject.deviceId, suppressed + 1);
24752
+ this.mutedSuppressed.set(bucket, suppressed + 1);
24726
24753
  return;
24727
24754
  }
24728
- this.lastMutedReportAt.set(subject.deviceId, now);
24729
- this.mutedSuppressed.set(subject.deviceId, 0);
24755
+ this.lastMutedReportAt.set(bucket, now);
24756
+ this.mutedSuppressed.set(bucket, 0);
24730
24757
  this.logger.info("notifications muted for this camera — event dropped before rule evaluation", {
24731
24758
  tags: { deviceId: subject.deviceId },
24732
24759
  meta: {
@@ -24738,16 +24765,23 @@ var NotificationCenter = class NotificationCenter {
24738
24765
  }
24739
24766
  });
24740
24767
  }
24741
- /** See the call site. Bounded to one line per device per window. */
24768
+ /** See the call site. Bounded to one line per {@link noMatchReportKey} bucket
24769
+ * per window — device, subject kind and class set, so one subject's verdict
24770
+ * cannot stand in for another's on the same camera ([D390]). */
24742
24771
  reportNoMatch(subject, kind, rejections, now) {
24743
- const last = this.lastNoMatchReportAt.get(subject.deviceId) ?? 0;
24744
- const suppressed = this.noMatchSuppressed.get(subject.deviceId) ?? 0;
24772
+ const bucket = noMatchReportKey({
24773
+ deviceId: subject.deviceId,
24774
+ kind,
24775
+ classNames: subject.classNames
24776
+ });
24777
+ const last = this.lastNoMatchReportAt.get(bucket) ?? 0;
24778
+ const suppressed = this.noMatchSuppressed.get(bucket) ?? 0;
24745
24779
  if (now - last < NO_MATCH_REPORT_INTERVAL_MS) {
24746
- this.noMatchSuppressed.set(subject.deviceId, suppressed + 1);
24780
+ this.noMatchSuppressed.set(bucket, suppressed + 1);
24747
24781
  return;
24748
24782
  }
24749
- this.lastNoMatchReportAt.set(subject.deviceId, now);
24750
- this.noMatchSuppressed.set(subject.deviceId, 0);
24783
+ this.lastNoMatchReportAt.set(bucket, now);
24784
+ this.noMatchSuppressed.set(bucket, 0);
24751
24785
  this.logger.info("event matched NO rule", {
24752
24786
  tags: { deviceId: subject.deviceId },
24753
24787
  meta: {
@@ -12807,6 +12807,23 @@ function recordToRow$2(subject, data) {
12807
12807
  };
12808
12808
  }
12809
12809
  //#endregion
12810
+ //#region src/notification-center/no-match-report-key.ts
12811
+ /**
12812
+ * Stand-in for "this subject named no class". A distinct token rather than an
12813
+ * empty string so a class-less subject occupies its own bucket instead of
12814
+ * sharing one with every other class-less kind by accident.
12815
+ */
12816
+ var NO_CLASSES = "∅";
12817
+ /**
12818
+ * The bucket key. Stable across calls, insensitive to class ORDER, DUPLICATES
12819
+ * and CASE — a detector that re-orders its class list, repeats one, or changes
12820
+ * its capitalisation must not fork the bucket and double the log volume.
12821
+ */
12822
+ function noMatchReportKey(subject) {
12823
+ const classes = [...new Set(subject.classNames.map((c) => c.toLowerCase()))].sort();
12824
+ return `${subject.deviceId}|${subject.kind}|${classes.length > 0 ? classes.join(",") : NO_CLASSES}`;
12825
+ }
12826
+ //#endregion
12810
12827
  //#region src/notification-center/occupancy-watcher.ts
12811
12828
  /** Sentinel key segments for the "no zone" (whole-frame) and "no class" scopes. */
12812
12829
  var FRAME_SCOPE = "@frame";
@@ -21923,10 +21940,15 @@ var NotificationCenter = class NotificationCenter {
21923
21940
  * which leaves those notifications text-only rather than broken.
21924
21941
  */
21925
21942
  stills;
21926
- /** Per-device rate limit for the "matched NO rule" report — see `reportNoMatch`. */
21943
+ /**
21944
+ * Rate limit for the "matched NO rule" report — see `reportNoMatch`. Keyed by
21945
+ * {@link noMatchReportKey} (device + subject kind + class set), NOT by device:
21946
+ * a person's verdict must never consume the window a cat's verdict needed on
21947
+ * the same camera ([D390]).
21948
+ */
21927
21949
  lastNoMatchReportAt = /* @__PURE__ */ new Map();
21928
21950
  noMatchSuppressed = /* @__PURE__ */ new Map();
21929
- /** Same rate-limit pair for the per-camera mute drop — see `reportMutedDrop`. */
21951
+ /** Same rate-limit pair, same key, for the per-camera mute drop — see `reportMutedDrop`. */
21930
21952
  lastMutedReportAt = /* @__PURE__ */ new Map();
21931
21953
  mutedSuppressed = /* @__PURE__ */ new Map();
21932
21954
  /**
@@ -24697,14 +24719,19 @@ var NotificationCenter = class NotificationCenter {
24697
24719
  * is identical: "why did this camera not notify me?".
24698
24720
  */
24699
24721
  reportMutedDrop(subject, kind, now) {
24700
- const last = this.lastMutedReportAt.get(subject.deviceId) ?? 0;
24701
- const suppressed = this.mutedSuppressed.get(subject.deviceId) ?? 0;
24722
+ const bucket = noMatchReportKey({
24723
+ deviceId: subject.deviceId,
24724
+ kind,
24725
+ classNames: subject.classNames
24726
+ });
24727
+ const last = this.lastMutedReportAt.get(bucket) ?? 0;
24728
+ const suppressed = this.mutedSuppressed.get(bucket) ?? 0;
24702
24729
  if (now - last < NO_MATCH_REPORT_INTERVAL_MS) {
24703
- this.mutedSuppressed.set(subject.deviceId, suppressed + 1);
24730
+ this.mutedSuppressed.set(bucket, suppressed + 1);
24704
24731
  return;
24705
24732
  }
24706
- this.lastMutedReportAt.set(subject.deviceId, now);
24707
- this.mutedSuppressed.set(subject.deviceId, 0);
24733
+ this.lastMutedReportAt.set(bucket, now);
24734
+ this.mutedSuppressed.set(bucket, 0);
24708
24735
  this.logger.info("notifications muted for this camera — event dropped before rule evaluation", {
24709
24736
  tags: { deviceId: subject.deviceId },
24710
24737
  meta: {
@@ -24716,16 +24743,23 @@ var NotificationCenter = class NotificationCenter {
24716
24743
  }
24717
24744
  });
24718
24745
  }
24719
- /** See the call site. Bounded to one line per device per window. */
24746
+ /** See the call site. Bounded to one line per {@link noMatchReportKey} bucket
24747
+ * per window — device, subject kind and class set, so one subject's verdict
24748
+ * cannot stand in for another's on the same camera ([D390]). */
24720
24749
  reportNoMatch(subject, kind, rejections, now) {
24721
- const last = this.lastNoMatchReportAt.get(subject.deviceId) ?? 0;
24722
- const suppressed = this.noMatchSuppressed.get(subject.deviceId) ?? 0;
24750
+ const bucket = noMatchReportKey({
24751
+ deviceId: subject.deviceId,
24752
+ kind,
24753
+ classNames: subject.classNames
24754
+ });
24755
+ const last = this.lastNoMatchReportAt.get(bucket) ?? 0;
24756
+ const suppressed = this.noMatchSuppressed.get(bucket) ?? 0;
24723
24757
  if (now - last < NO_MATCH_REPORT_INTERVAL_MS) {
24724
- this.noMatchSuppressed.set(subject.deviceId, suppressed + 1);
24758
+ this.noMatchSuppressed.set(bucket, suppressed + 1);
24725
24759
  return;
24726
24760
  }
24727
- this.lastNoMatchReportAt.set(subject.deviceId, now);
24728
- this.noMatchSuppressed.set(subject.deviceId, 0);
24761
+ this.lastNoMatchReportAt.set(bucket, now);
24762
+ this.noMatchSuppressed.set(bucket, 0);
24729
24763
  this.logger.info("event matched NO rule", {
24730
24764
  tags: { deviceId: subject.deviceId },
24731
24765
  meta: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-post-analysis",
3
- "version": "1.2.198",
3
+ "version": "1.2.199",
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",