@camstack/addon-post-analysis 1.2.197 → 1.2.198

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.
@@ -1,4 +1,4 @@
1
- import { $ as audioModeOf, A as NcSnoozeSchema, At as BaseAddon, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as boolean, C as NcConditionDescriptorSchema, Ct as sceneMonitorCapability, D as NcRuleTargetSchema, Dt as videoclipsCapability, E as NcRuleSchema, Et as vectorDimFromBase64, Ft as isDeviceScopedCap, G as TimelapseRulePatchSchema, Gt as partialRecord, H as SceneMonitorSchema, Ht as literal, I as RECORDING_EXPORT_MAX_READ_BYTES, It as nodePin, J as VISIT_MERGE_GAP_MS, Jt as unknown, K as TimelapseRuleSchema, Kt as record, L as RetrainStatusSchema, Lt as sleep$1, M as NcSystemEventKindSchema, Mt as DeviceType, N as NcTaxonomySchema, Nt as createEvent, O as NcScheduleSchema, Ot as zoneAnalyticsCapability, P as OpsLogEntrySchema, Pt as hydrateSchema, Q as audioMetricsCapability, Rt as _enum, S as NC_TAXONOMY, T as NcRulePatchSchema, Tt as systemEventFilterApplies, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as number, V as SCENE_DIVERGED, Vt as discriminatedUnion, W as TimelapseRuleInputSchema, Wt as object, X as alarmPanelCapability, Y as addonWidgetsSourceCapability, Yt as EventCategory, Z as assertTimelapseCadences, _ as MediaFileKindEnum, _t as pickClusterStepModels, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as readDeviceStateFrom, c as DETECTION_MACRO_CLASSES, ct as faceGalleryCapability, d as EVENT_KIND_BY_CAP, dt as isDetectionMacroClass, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isScheduleActive, g as MACRO_LABELS, h as LabelAttributionSchema, ht as notificationRulesCapability, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as CamProfileSchema, k as NcSnoozeInputSchema, kt as errMsg$1, lt as failureContributionCapability, m as FailureCounters, mt as kebabToCamel, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isSourceCap, q as TrackSourceSchema, qt as string, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, v as NC_ALARM_SYSTEM_EVENT_KINDS, vt as pipelineAnalyticsCapability, w as NcRuleInputSchema, wt as subKindsOf, xt as readTimelapseGeneratedAt, y as NC_CONDITION_CATALOG, yt as plateGalleryCapability, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as array } from "../dist-DT2v4pR9.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-BrszC4uJ.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";
@@ -9,6 +9,45 @@ import { execFile } from "node:child_process";
9
9
  import sharp from "sharp";
10
10
  import os from "node:os";
11
11
  import { Buffer as Buffer$1 } from "node:buffer";
12
+ //#region src/pipeline-analytics/media-occupancy.ts
13
+ /** The bare type ref legacy NULL-stamped media rows belong to. */
14
+ var LEGACY_MEDIA_TYPE = "eventMedia";
15
+ /**
16
+ * Which of the requested locations the legacy `eventMedia` rows belong to, or
17
+ * `null` when it cannot be decided — the same rule `resolveRef` applies, and
18
+ * the same refusal when it is ambiguous.
19
+ */
20
+ function legacyMediaLocationOf(locationIds) {
21
+ const ofType = locationIds.filter((id) => id.startsWith(`${LEGACY_MEDIA_TYPE}:`));
22
+ if (ofType.length === 1) return ofType[0] ?? null;
23
+ return ofType.find((id) => id === `eventMedia:default`) ?? null;
24
+ }
25
+ /**
26
+ * The occupancy reports for one measurement.
27
+ *
28
+ * A location with nothing on it is OMITTED, not reported as zero: only the
29
+ * orchestrator knows whether nobody reported for a location or every reporter
30
+ * holds nothing there, and it is the one that must be able to tell them apart.
31
+ */
32
+ function mediaOccupancyReports(footprint, locationIds, measuredAtMs) {
33
+ const bytesByLocation = new Map(footprint.byLocation);
34
+ const legacyTarget = footprint.legacyNullBytes > 0 ? legacyMediaLocationOf(locationIds) : null;
35
+ if (legacyTarget !== null) bytesByLocation.set(legacyTarget, (bytesByLocation.get(legacyTarget) ?? 0) + footprint.legacyNullBytes);
36
+ const reports = [];
37
+ for (const [locationId, ownedBytes] of bytesByLocation) {
38
+ if (ownedBytes <= 0) continue;
39
+ reports.push({
40
+ locationId,
41
+ ownedBytes,
42
+ measuredAtMs
43
+ });
44
+ }
45
+ return {
46
+ reports,
47
+ unattributedBytes: legacyTarget === null ? footprint.legacyNullBytes : 0
48
+ };
49
+ }
50
+ //#endregion
12
51
  //#region src/notification-center/action-token.ts
13
52
  /**
14
53
  * The authority behind a notification button.
@@ -28857,6 +28896,50 @@ var MediaStore = class {
28857
28896
  return out;
28858
28897
  }
28859
28898
  /**
28899
+ * Bytes this store holds on each of the named storage locations (D388).
28900
+ *
28901
+ * One indexed aggregate per location (`idx_media_location`), plus one for the
28902
+ * legacy NULL-stamped rows — `locationId IS NULL` means "wherever the bare
28903
+ * `eventMedia` type ref resolves", and the CALLER attributes those, because
28904
+ * resolving a bare type ref is the orchestrator's rule and not this store's.
28905
+ *
28906
+ * A location this store holds nothing on is OMITTED, never reported as zero:
28907
+ * the difference between "we hold nothing here" and "nobody asked" is the
28908
+ * whole point of the occupancy contract, and only the caller can tell which
28909
+ * it is. A read that fails THROWS rather than degrading to zeros — the same
28910
+ * rule `footprintByDevice` follows, for the same reason.
28911
+ */
28912
+ async footprintByLocation(locationIds) {
28913
+ const byLocation = /* @__PURE__ */ new Map();
28914
+ for (const locationId of locationIds) {
28915
+ const agg = await this.store.aggregate.query({
28916
+ collection: MEDIA_COLLECTION,
28917
+ fields: [{
28918
+ as: "bytes",
28919
+ field: "sizeBytes",
28920
+ op: "sum"
28921
+ }],
28922
+ filter: { where: { locationId } }
28923
+ });
28924
+ const bytes = agg.values["bytes"] ?? 0;
28925
+ if (agg.count === 0 || bytes <= 0) continue;
28926
+ byLocation.set(locationId, bytes);
28927
+ }
28928
+ const legacy = await this.store.aggregate.query({
28929
+ collection: MEDIA_COLLECTION,
28930
+ fields: [{
28931
+ as: "bytes",
28932
+ field: "sizeBytes",
28933
+ op: "sum"
28934
+ }],
28935
+ filter: { where: { locationId: null } }
28936
+ });
28937
+ return {
28938
+ byLocation,
28939
+ legacyNullBytes: legacy.count === 0 ? 0 : legacy.values["bytes"] ?? 0
28940
+ };
28941
+ }
28942
+ /**
28860
28943
  * The media footprint broken down by KIND — fleet-wide, or for one camera.
28861
28944
  *
28862
28945
  * `footprintByDevice` answers "how much is where"; this answers "how much is
@@ -42066,21 +42149,16 @@ async function wipeClassSubtrees(input) {
42066
42149
  }
42067
42150
  //#endregion
42068
42151
  //#region src/pipeline-analytics/retention/location-discard-policy.ts
42069
- /**
42070
- * Server-side eligibility for discarding a frozen location's leftover
42071
- * class subtree. Same rules as the admin-ui card: frozen, known class,
42072
- * writable sibling of the same type. The last writable of a type is
42073
- * refused here even if the UI is bypassed.
42074
- */
42075
42152
  var DISCARD_SUBTREES = {
42076
42153
  eventMedia: ["events"],
42077
42154
  recordingsLow: ["low"],
42078
42155
  recordings: ["high", "mid"]
42079
42156
  };
42157
+ /** Frozen = the system will not remove this footage on its own: eviction
42158
+ * policy `never` (D385), i.e. `readonly` or `disabled`. A `drain` location is
42159
+ * NOT frozen — the ratchet is already removing it. */
42080
42160
  function locationIsFrozen(location) {
42081
- const enabled = location.enabled !== false;
42082
- const readOnly = location.config["readOnly"] === true;
42083
- return !enabled || readOnly;
42161
+ return evictionPolicyOfLocation(location) === "never";
42084
42162
  }
42085
42163
  function discardEligible(location, siblings) {
42086
42164
  if (!(location.type in DISCARD_SUBTREES)) return false;
@@ -42088,9 +42166,7 @@ function discardEligible(location, siblings) {
42088
42166
  return siblings.some((s) => {
42089
42167
  if (s.id === location.id) return false;
42090
42168
  if (s.type !== location.type) return false;
42091
- const enabled = s.enabled !== false;
42092
- const readOnly = s.config["readOnly"] === true;
42093
- return enabled && !readOnly;
42169
+ return mayWriteToLocation(s);
42094
42170
  });
42095
42171
  }
42096
42172
  function assertDiscardAllowed(location, all, localNodeId) {
@@ -50719,7 +50795,10 @@ var MediaCaptureLogAggregator = class {
50719
50795
  * a restart that switches disk splits nothing that reads wrong.
50720
50796
  */
50721
50797
  function pickWritableMediaLocation(locations, stickyId) {
50722
- const writable = locations.filter((l) => l.enabled && !l.readOnly);
50798
+ const writable = locations.filter((l) => mayWriteToLocation({
50799
+ mode: l.mode,
50800
+ config: {}
50801
+ }));
50723
50802
  if (writable.length === 0) return null;
50724
50803
  const keep = stickyId === void 0 ? void 0 : writable.find((l) => l.id === stickyId);
50725
50804
  if (keep) return keep.id;
@@ -68729,9 +68808,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
68729
68808
  }
68730
68809
  /**
68731
68810
  * Resolves which `eventMedia` location a NEW footage-class blob should be
68732
- * written to (`MediaStoreDeps.resolveEventMediaLocation`). Reflects
68733
- * `config.readOnly` — an operator draining a location marks it read-only,
68734
- * and new writes must never land there (the relocate mover, exposed as
68811
+ * written to (`MediaStoreDeps.resolveEventMediaLocation`). Reflects the
68812
+ * location's `mode` (D385) a location the operator has taken out of the
68813
+ * write set never receives new blobs (the relocate mover, exposed as
68735
68814
  * `relocateMedia`, empties what is already on it in the background).
68736
68815
  *
68737
68816
  * Cached after the first successful resolution — this is a per-blob write
@@ -68746,6 +68825,32 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
68746
68825
  * a test harness that never wires `api.storage`) must not turn every media
68747
68826
  * write into a hard failure the way "no writable location" does.
68748
68827
  */
68828
+ /**
68829
+ * The `storage-occupancy` answer for event media (D388) — bytes HELD on each
68830
+ * named location, not bytes retention would be willing to remove.
68831
+ *
68832
+ * Answered from ONE indexed aggregate per location. A store that is not up
68833
+ * yet reports NOTHING, which reads as unknown; it never reports zeros, which
68834
+ * would claim the disks are empty. Legacy NULL-stamped rows are attributed by
68835
+ * `mediaOccupancyReports`, and bytes it cannot place are LOGGED rather than
68836
+ * spread over the candidates — a number no disk agrees with is worse than an
68837
+ * absent one.
68838
+ */
68839
+ async mediaOccupancy(locationIds) {
68840
+ const store = this.mediaStore;
68841
+ if (!store) return [];
68842
+ const { reports, unattributedBytes } = mediaOccupancyReports(await store.footprintByLocation(locationIds), locationIds, Date.now());
68843
+ if (unattributedBytes > 0 && !this.mediaOccupancyUnattributedLogged) {
68844
+ this.mediaOccupancyUnattributedLogged = true;
68845
+ this.ctx.logger.warn("pipeline-analytics: event-media bytes could not be attributed to a location — the legacy `eventMedia` type ref resolves to no single location, so they are reported NOWHERE rather than onto a disk that does not hold them", { meta: {
68846
+ unattributedBytes,
68847
+ locationIds: [...locationIds]
68848
+ } });
68849
+ }
68850
+ return reports;
68851
+ }
68852
+ /** One line per process: this repeats on every occupancy refresh otherwise. */
68853
+ mediaOccupancyUnattributedLogged = false;
68749
68854
  async resolveEventMediaWriteLocation(api, logger, deviceId) {
68750
68855
  const cached = this.eventMediaWriteLocationCache;
68751
68856
  if (cached !== null) return cached;
@@ -68759,8 +68864,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
68759
68864
  }
68760
68865
  const picked = pickWritableMediaLocation(locations.map((l) => ({
68761
68866
  id: l.id,
68762
- readOnly: l.config["readOnly"] === true,
68763
- enabled: l.enabled !== false,
68867
+ mode: resolveLocationMode(l),
68764
68868
  headroomBytes: l.capacity?.availableBytes ?? 0
68765
68869
  })), this.lastEventMediaWritePick ?? void 0);
68766
68870
  if (picked === null) {
@@ -70717,6 +70821,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
70717
70821
  capability: notificationRulesCapability,
70718
70822
  provider: this.notificationCenter.buildProvider()
70719
70823
  }] : [],
70824
+ {
70825
+ capability: storageOccupancyCapability,
70826
+ provider: { getOccupancy: async (input) => this.mediaOccupancy(input.locationIds) }
70827
+ },
70720
70828
  {
70721
70829
  capability: zoneAnalyticsCapability,
70722
70830
  provider: providers.zoneAnalytics
@@ -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-BQGYsovE.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CTKbm1Jm.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.197",
3
+ "version": "1.2.198",
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",
@@ -101,6 +101,10 @@
101
101
  },
102
102
  {
103
103
  "name": "failure-contribution"
104
+ },
105
+ {
106
+ "name": "storage-occupancy",
107
+ "optional": true
104
108
  }
105
109
  ],
106
110
  "storageLocations": [