@camstack/addon-post-analysis 1.2.126 → 1.2.127

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.
@@ -46374,6 +46374,50 @@ var AUDIO_COLUMNS = [
46374
46374
  type: "JSON"
46375
46375
  }
46376
46376
  ];
46377
+ /**
46378
+ * The columns `slimMotion` reads. `id` is the primary key — always returned,
46379
+ * never part of a projection.
46380
+ *
46381
+ * What it leaves behind is the point: `regions` is a JSON array with one entry
46382
+ * per motion rectangle, and it is the only reason a motion row is not tiny.
46383
+ * `frameWidth` / `frameHeight` go with it — they exist to interpret `regions`
46384
+ * and mean nothing without it.
46385
+ */
46386
+ var SLIM_MOTION_COLUMNS = [
46387
+ "deviceId",
46388
+ "timestamp",
46389
+ "regionCount"
46390
+ ];
46391
+ /**
46392
+ * The columns `slimObject` reads — and ONLY those.
46393
+ *
46394
+ * Four JSON columns are dropped (`bbox`, `zones`, `labelMeta`, `subLabelMeta`)
46395
+ * plus the numbers that only exist to argue about a value after the fact
46396
+ * (`confidence`, `labelScore`, `subLabelScore`) and the frame geometry that,
46397
+ * like motion's, is meaningless without the box it describes.
46398
+ *
46399
+ * `label` / `subLabel` stay because the timeline renders `subLabel ?? label`,
46400
+ * which cannot be evaluated from half a row. `frameId` / `trackId` /
46401
+ * `importance` stay because they are the grouping and sort keys the events page
46402
+ * needs — all three are light.
46403
+ *
46404
+ * ⚠ `slimObject` calls `readTieredLabelColumns`, which reads all SIX tiered
46405
+ * columns and returns the four unprojected ones as `undefined`. That is
46406
+ * harmless here only because `slimObject` uses exactly `label` and `subLabel`
46407
+ * from what it gets back. If it ever starts reading a score, this list is the
46408
+ * thing that has to learn about it — the failure would otherwise be a score
46409
+ * that reads as absent on every slim row.
46410
+ */
46411
+ var SLIM_OBJECT_COLUMNS = [
46412
+ "deviceId",
46413
+ "timestamp",
46414
+ "className",
46415
+ "frameId",
46416
+ "trackId",
46417
+ "importance",
46418
+ "label",
46419
+ "subLabel"
46420
+ ];
46377
46421
  var COMMON_INDEXES = (prefix) => [{
46378
46422
  name: `idx_${prefix}_device_ts`,
46379
46423
  columns: ["deviceId", "timestamp"]
@@ -46557,6 +46601,19 @@ var EventStore = class {
46557
46601
  filter
46558
46602
  })]);
46559
46603
  }
46604
+ /**
46605
+ * Spreadable `columns` for a slim read — omitted entirely for `full`.
46606
+ *
46607
+ * The projection is decided in ONE place per collection ({@link
46608
+ * SLIM_MOTION_COLUMNS} / {@link SLIM_OBJECT_COLUMNS}) because a projection is
46609
+ * a promise between the SELECT and the mapper: the day they disagree nothing
46610
+ * fails. SQLite returns the row without the column, `slimObject` reads
46611
+ * `data['className']`, gets `undefined`, and hands back an event whose class
46612
+ * is missing — indistinguishable from a row that never had one.
46613
+ */
46614
+ slimColumns(projection, columns) {
46615
+ return projection === "slim" ? { columns } : {};
46616
+ }
46560
46617
  buildFilter(q) {
46561
46618
  const filter = {
46562
46619
  where: { deviceId: q.deviceId },
@@ -46572,7 +46629,8 @@ var EventStore = class {
46572
46629
  async queryMotion(q) {
46573
46630
  const rows = await this.store.query.query({
46574
46631
  collection: MOTION_EVENTS_COLLECTION,
46575
- filter: this.buildFilter(q)
46632
+ filter: this.buildFilter(q),
46633
+ ...this.slimColumns(q.projection, SLIM_MOTION_COLUMNS)
46576
46634
  });
46577
46635
  if (q.projection === "slim") return rows.map((r) => slimMotion(r.id, stripNulls$1(r.data)));
46578
46636
  return rows.map((r) => {
@@ -46642,7 +46700,8 @@ var EventStore = class {
46642
46700
  }
46643
46701
  const rows = await this.store.query.query({
46644
46702
  collection: OBJECT_EVENTS_COLLECTION,
46645
- filter
46703
+ filter,
46704
+ ...this.slimColumns(q.projection, SLIM_OBJECT_COLUMNS)
46646
46705
  });
46647
46706
  if (q.projection === "slim") return rows.map((r) => slimObject(r.id, stripNulls$1(r.data)));
46648
46707
  return rows.map((r) => {
@@ -49301,6 +49360,16 @@ function trackColumnsFor(projection, needsPositions = false) {
49301
49360
  if (projection !== "slim") return void 0;
49302
49361
  return needsPositions ? [...SLIM_TRACK_COLUMN_NAMES, "positions"] : SLIM_TRACK_COLUMN_NAMES;
49303
49362
  }
49363
+ /**
49364
+ * The SELECT list for an IDS-ONLY recent page: the primary key (free, always
49365
+ * returned) plus `lastSeen`.
49366
+ *
49367
+ * `lastSeen` is not an optimisation to shave off later — {@link
49368
+ * TrackStore.recentPage} orders on it, windows on it and breaks
49369
+ * same-millisecond ties with it. Reading ids without it would produce a page in
49370
+ * whatever order the engine felt like and a cursor that cannot resume.
49371
+ */
49372
+ var RECENT_IDS_COLUMNS = ["lastSeen"];
49304
49373
  /** Spreadable `columns` for a store query — omitted entirely for `full`. */
49305
49374
  function projectionArg(projection, needsPositions = false) {
49306
49375
  const columns = trackColumnsFor(projection, needsPositions);
@@ -50555,10 +50624,48 @@ var TrackStore = class {
50555
50624
  * Errors propagate (no partial merges): a failed device query fails the
50556
50625
  * page rather than silently returning an incomplete window.
50557
50626
  */
50627
+ /**
50628
+ * A page of RECENT track ids, and nothing else.
50629
+ *
50630
+ * Two callers only ever wanted the strings — `collectLiveTrackIds` (the
50631
+ * orphan audit's live set) and `listTrackIdPage` (the embedding rebuild's
50632
+ * cursor walk) — and both were reading whole tracks to build them. This reads
50633
+ * the primary key plus `lastSeen`, which is not a saving to be traded away:
50634
+ * the cursor, the descending order and the same-millisecond tie-break are all
50635
+ * defined on `lastSeen`, so a page without it would be unordered and
50636
+ * unresumable.
50637
+ *
50638
+ * It shares {@link recentPage} with {@link queryRecent} rather than repeating
50639
+ * the window/cursor arithmetic. Two copies of a paging rule is how one of
50640
+ * them learns about a new bound and the other does not.
50641
+ */
50642
+ async queryRecentIds(params) {
50643
+ const { page, nextCursor } = await this.recentPage(params, RECENT_IDS_COLUMNS);
50644
+ return {
50645
+ trackIds: page.map((r) => r.id),
50646
+ nextCursor
50647
+ };
50648
+ }
50558
50649
  async queryRecent(params) {
50650
+ const { page, nextCursor } = await this.recentPage(params, trackColumnsFor(params.projection));
50651
+ return {
50652
+ tracks: page.map((r) => this.rowToTrack(r.id, r.data, params.projection)),
50653
+ nextCursor
50654
+ };
50655
+ }
50656
+ /**
50657
+ * The window, cursor and fan-out arithmetic behind every "recent tracks"
50658
+ * read — shared so the SELECT list is the only thing a caller varies.
50659
+ *
50660
+ * `columns` is passed straight through to the engine (`undefined` = the whole
50661
+ * row). Everything else here is projection-agnostic on purpose: which rows a
50662
+ * page contains, and in what order, must not depend on how much of each row
50663
+ * the caller asked to read.
50664
+ */
50665
+ async recentPage(params, columns) {
50559
50666
  const deviceIds = [...new Set(params.deviceIds)];
50560
50667
  if (deviceIds.length === 0) return {
50561
- tracks: [],
50668
+ page: [],
50562
50669
  nextCursor: null
50563
50670
  };
50564
50671
  const requested = Math.min(Math.max(params.limit ?? RECENT_DEFAULT_LIMIT, 1), RECENT_MAX_LIMIT);
@@ -50582,7 +50689,7 @@ var TrackStore = class {
50582
50689
  const winHi = params.until ?? Number.MAX_SAFE_INTEGER;
50583
50690
  const hi = cursor !== null ? Math.min(cursor.lastSeen, winHi) : winHi;
50584
50691
  if (hi < lo) return {
50585
- tracks: [],
50692
+ page: [],
50586
50693
  nextCursor: null
50587
50694
  };
50588
50695
  const stationaryExclusion = params.excludeStationaryPromotions === true ? { whereNot: { closedReason: "stationary-promotion" } } : {};
@@ -50598,7 +50705,7 @@ var TrackStore = class {
50598
50705
  },
50599
50706
  ...pageLimit !== void 0 ? { limit: pageLimit } : {}
50600
50707
  },
50601
- ...projectionArg(params.projection)
50708
+ ...columns === void 0 ? {} : { columns }
50602
50709
  });
50603
50710
  const perDevice = await Promise.all(deviceIds.map(async (deviceId) => {
50604
50711
  if (cursor === null || cursor.lastSeen > hi) return fetchRange(deviceId, [lo, hi], limit + 1);
@@ -50622,10 +50729,9 @@ var TrackStore = class {
50622
50729
  }
50623
50730
  candidates.sort(compareRecentDesc);
50624
50731
  const page = candidates.slice(0, limit);
50625
- const tracks = page.map((r) => this.rowToTrack(r.id, r.data, params.projection));
50626
50732
  const last = page[page.length - 1];
50627
50733
  return {
50628
- tracks,
50734
+ page,
50629
50735
  nextCursor: candidates.length > limit && last !== void 0 ? encodeRecentCursor({
50630
50736
  lastSeen: last.lastSeen,
50631
50737
  trackId: last.id
@@ -64417,11 +64523,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
64417
64523
  const trackStore = this.trackStore;
64418
64524
  if (!trackStore) return live;
64419
64525
  for (const deviceId of deviceIds) {
64420
- const page = await trackStore.queryRecent({
64526
+ const page = await trackStore.queryRecentIds({
64421
64527
  deviceIds: [deviceId],
64422
64528
  limit: EMBEDDING_ORPHAN_SCAN_LIMIT
64423
64529
  });
64424
- for (const track of page.tracks) live.add(track.trackId);
64530
+ for (const trackId of page.trackIds) live.add(trackId);
64425
64531
  }
64426
64532
  return live;
64427
64533
  }
@@ -64744,19 +64850,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
64744
64850
  logger: this.ctx.logger
64745
64851
  }));
64746
64852
  const deps = {
64747
- listTrackIdPage: async (cursor) => {
64748
- const page = await trackStore.queryRecent({
64749
- deviceIds,
64750
- ...input.since !== void 0 ? { since: input.since } : {},
64751
- ...input.until !== void 0 ? { until: input.until } : {},
64752
- ...cursor !== null ? { cursor } : {},
64753
- limit: REBUILD_PAGE_SIZE
64754
- });
64755
- return {
64756
- trackIds: page.tracks.map((t) => t.trackId),
64757
- nextCursor: page.nextCursor
64758
- };
64759
- },
64853
+ listTrackIdPage: async (cursor) => trackStore.queryRecentIds({
64854
+ deviceIds,
64855
+ ...input.since !== void 0 ? { since: input.since } : {},
64856
+ ...input.until !== void 0 ? { until: input.until } : {},
64857
+ ...cursor !== null ? { cursor } : {},
64858
+ limit: REBUILD_PAGE_SIZE
64859
+ }),
64760
64860
  loadSource: (trackId) => loadRebuildSource({
64761
64861
  media: mediaStore,
64762
64862
  events: eventStore
@@ -46311,6 +46311,50 @@ var AUDIO_COLUMNS = [
46311
46311
  type: "JSON"
46312
46312
  }
46313
46313
  ];
46314
+ /**
46315
+ * The columns `slimMotion` reads. `id` is the primary key — always returned,
46316
+ * never part of a projection.
46317
+ *
46318
+ * What it leaves behind is the point: `regions` is a JSON array with one entry
46319
+ * per motion rectangle, and it is the only reason a motion row is not tiny.
46320
+ * `frameWidth` / `frameHeight` go with it — they exist to interpret `regions`
46321
+ * and mean nothing without it.
46322
+ */
46323
+ var SLIM_MOTION_COLUMNS = [
46324
+ "deviceId",
46325
+ "timestamp",
46326
+ "regionCount"
46327
+ ];
46328
+ /**
46329
+ * The columns `slimObject` reads — and ONLY those.
46330
+ *
46331
+ * Four JSON columns are dropped (`bbox`, `zones`, `labelMeta`, `subLabelMeta`)
46332
+ * plus the numbers that only exist to argue about a value after the fact
46333
+ * (`confidence`, `labelScore`, `subLabelScore`) and the frame geometry that,
46334
+ * like motion's, is meaningless without the box it describes.
46335
+ *
46336
+ * `label` / `subLabel` stay because the timeline renders `subLabel ?? label`,
46337
+ * which cannot be evaluated from half a row. `frameId` / `trackId` /
46338
+ * `importance` stay because they are the grouping and sort keys the events page
46339
+ * needs — all three are light.
46340
+ *
46341
+ * ⚠ `slimObject` calls `readTieredLabelColumns`, which reads all SIX tiered
46342
+ * columns and returns the four unprojected ones as `undefined`. That is
46343
+ * harmless here only because `slimObject` uses exactly `label` and `subLabel`
46344
+ * from what it gets back. If it ever starts reading a score, this list is the
46345
+ * thing that has to learn about it — the failure would otherwise be a score
46346
+ * that reads as absent on every slim row.
46347
+ */
46348
+ var SLIM_OBJECT_COLUMNS = [
46349
+ "deviceId",
46350
+ "timestamp",
46351
+ "className",
46352
+ "frameId",
46353
+ "trackId",
46354
+ "importance",
46355
+ "label",
46356
+ "subLabel"
46357
+ ];
46314
46358
  var COMMON_INDEXES = (prefix) => [{
46315
46359
  name: `idx_${prefix}_device_ts`,
46316
46360
  columns: ["deviceId", "timestamp"]
@@ -46494,6 +46538,19 @@ var EventStore = class {
46494
46538
  filter
46495
46539
  })]);
46496
46540
  }
46541
+ /**
46542
+ * Spreadable `columns` for a slim read — omitted entirely for `full`.
46543
+ *
46544
+ * The projection is decided in ONE place per collection ({@link
46545
+ * SLIM_MOTION_COLUMNS} / {@link SLIM_OBJECT_COLUMNS}) because a projection is
46546
+ * a promise between the SELECT and the mapper: the day they disagree nothing
46547
+ * fails. SQLite returns the row without the column, `slimObject` reads
46548
+ * `data['className']`, gets `undefined`, and hands back an event whose class
46549
+ * is missing — indistinguishable from a row that never had one.
46550
+ */
46551
+ slimColumns(projection, columns) {
46552
+ return projection === "slim" ? { columns } : {};
46553
+ }
46497
46554
  buildFilter(q) {
46498
46555
  const filter = {
46499
46556
  where: { deviceId: q.deviceId },
@@ -46509,7 +46566,8 @@ var EventStore = class {
46509
46566
  async queryMotion(q) {
46510
46567
  const rows = await this.store.query.query({
46511
46568
  collection: MOTION_EVENTS_COLLECTION,
46512
- filter: this.buildFilter(q)
46569
+ filter: this.buildFilter(q),
46570
+ ...this.slimColumns(q.projection, SLIM_MOTION_COLUMNS)
46513
46571
  });
46514
46572
  if (q.projection === "slim") return rows.map((r) => slimMotion(r.id, stripNulls$1(r.data)));
46515
46573
  return rows.map((r) => {
@@ -46579,7 +46637,8 @@ var EventStore = class {
46579
46637
  }
46580
46638
  const rows = await this.store.query.query({
46581
46639
  collection: OBJECT_EVENTS_COLLECTION,
46582
- filter
46640
+ filter,
46641
+ ...this.slimColumns(q.projection, SLIM_OBJECT_COLUMNS)
46583
46642
  });
46584
46643
  if (q.projection === "slim") return rows.map((r) => slimObject(r.id, stripNulls$1(r.data)));
46585
46644
  return rows.map((r) => {
@@ -49238,6 +49297,16 @@ function trackColumnsFor(projection, needsPositions = false) {
49238
49297
  if (projection !== "slim") return void 0;
49239
49298
  return needsPositions ? [...SLIM_TRACK_COLUMN_NAMES, "positions"] : SLIM_TRACK_COLUMN_NAMES;
49240
49299
  }
49300
+ /**
49301
+ * The SELECT list for an IDS-ONLY recent page: the primary key (free, always
49302
+ * returned) plus `lastSeen`.
49303
+ *
49304
+ * `lastSeen` is not an optimisation to shave off later — {@link
49305
+ * TrackStore.recentPage} orders on it, windows on it and breaks
49306
+ * same-millisecond ties with it. Reading ids without it would produce a page in
49307
+ * whatever order the engine felt like and a cursor that cannot resume.
49308
+ */
49309
+ var RECENT_IDS_COLUMNS = ["lastSeen"];
49241
49310
  /** Spreadable `columns` for a store query — omitted entirely for `full`. */
49242
49311
  function projectionArg(projection, needsPositions = false) {
49243
49312
  const columns = trackColumnsFor(projection, needsPositions);
@@ -50492,10 +50561,48 @@ var TrackStore = class {
50492
50561
  * Errors propagate (no partial merges): a failed device query fails the
50493
50562
  * page rather than silently returning an incomplete window.
50494
50563
  */
50564
+ /**
50565
+ * A page of RECENT track ids, and nothing else.
50566
+ *
50567
+ * Two callers only ever wanted the strings — `collectLiveTrackIds` (the
50568
+ * orphan audit's live set) and `listTrackIdPage` (the embedding rebuild's
50569
+ * cursor walk) — and both were reading whole tracks to build them. This reads
50570
+ * the primary key plus `lastSeen`, which is not a saving to be traded away:
50571
+ * the cursor, the descending order and the same-millisecond tie-break are all
50572
+ * defined on `lastSeen`, so a page without it would be unordered and
50573
+ * unresumable.
50574
+ *
50575
+ * It shares {@link recentPage} with {@link queryRecent} rather than repeating
50576
+ * the window/cursor arithmetic. Two copies of a paging rule is how one of
50577
+ * them learns about a new bound and the other does not.
50578
+ */
50579
+ async queryRecentIds(params) {
50580
+ const { page, nextCursor } = await this.recentPage(params, RECENT_IDS_COLUMNS);
50581
+ return {
50582
+ trackIds: page.map((r) => r.id),
50583
+ nextCursor
50584
+ };
50585
+ }
50495
50586
  async queryRecent(params) {
50587
+ const { page, nextCursor } = await this.recentPage(params, trackColumnsFor(params.projection));
50588
+ return {
50589
+ tracks: page.map((r) => this.rowToTrack(r.id, r.data, params.projection)),
50590
+ nextCursor
50591
+ };
50592
+ }
50593
+ /**
50594
+ * The window, cursor and fan-out arithmetic behind every "recent tracks"
50595
+ * read — shared so the SELECT list is the only thing a caller varies.
50596
+ *
50597
+ * `columns` is passed straight through to the engine (`undefined` = the whole
50598
+ * row). Everything else here is projection-agnostic on purpose: which rows a
50599
+ * page contains, and in what order, must not depend on how much of each row
50600
+ * the caller asked to read.
50601
+ */
50602
+ async recentPage(params, columns) {
50496
50603
  const deviceIds = [...new Set(params.deviceIds)];
50497
50604
  if (deviceIds.length === 0) return {
50498
- tracks: [],
50605
+ page: [],
50499
50606
  nextCursor: null
50500
50607
  };
50501
50608
  const requested = Math.min(Math.max(params.limit ?? RECENT_DEFAULT_LIMIT, 1), RECENT_MAX_LIMIT);
@@ -50519,7 +50626,7 @@ var TrackStore = class {
50519
50626
  const winHi = params.until ?? Number.MAX_SAFE_INTEGER;
50520
50627
  const hi = cursor !== null ? Math.min(cursor.lastSeen, winHi) : winHi;
50521
50628
  if (hi < lo) return {
50522
- tracks: [],
50629
+ page: [],
50523
50630
  nextCursor: null
50524
50631
  };
50525
50632
  const stationaryExclusion = params.excludeStationaryPromotions === true ? { whereNot: { closedReason: "stationary-promotion" } } : {};
@@ -50535,7 +50642,7 @@ var TrackStore = class {
50535
50642
  },
50536
50643
  ...pageLimit !== void 0 ? { limit: pageLimit } : {}
50537
50644
  },
50538
- ...projectionArg(params.projection)
50645
+ ...columns === void 0 ? {} : { columns }
50539
50646
  });
50540
50647
  const perDevice = await Promise.all(deviceIds.map(async (deviceId) => {
50541
50648
  if (cursor === null || cursor.lastSeen > hi) return fetchRange(deviceId, [lo, hi], limit + 1);
@@ -50559,10 +50666,9 @@ var TrackStore = class {
50559
50666
  }
50560
50667
  candidates.sort(compareRecentDesc);
50561
50668
  const page = candidates.slice(0, limit);
50562
- const tracks = page.map((r) => this.rowToTrack(r.id, r.data, params.projection));
50563
50669
  const last = page[page.length - 1];
50564
50670
  return {
50565
- tracks,
50671
+ page,
50566
50672
  nextCursor: candidates.length > limit && last !== void 0 ? encodeRecentCursor({
50567
50673
  lastSeen: last.lastSeen,
50568
50674
  trackId: last.id
@@ -64354,11 +64460,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
64354
64460
  const trackStore = this.trackStore;
64355
64461
  if (!trackStore) return live;
64356
64462
  for (const deviceId of deviceIds) {
64357
- const page = await trackStore.queryRecent({
64463
+ const page = await trackStore.queryRecentIds({
64358
64464
  deviceIds: [deviceId],
64359
64465
  limit: EMBEDDING_ORPHAN_SCAN_LIMIT
64360
64466
  });
64361
- for (const track of page.tracks) live.add(track.trackId);
64467
+ for (const trackId of page.trackIds) live.add(trackId);
64362
64468
  }
64363
64469
  return live;
64364
64470
  }
@@ -64681,19 +64787,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
64681
64787
  logger: this.ctx.logger
64682
64788
  }));
64683
64789
  const deps = {
64684
- listTrackIdPage: async (cursor) => {
64685
- const page = await trackStore.queryRecent({
64686
- deviceIds,
64687
- ...input.since !== void 0 ? { since: input.since } : {},
64688
- ...input.until !== void 0 ? { until: input.until } : {},
64689
- ...cursor !== null ? { cursor } : {},
64690
- limit: REBUILD_PAGE_SIZE
64691
- });
64692
- return {
64693
- trackIds: page.tracks.map((t) => t.trackId),
64694
- nextCursor: page.nextCursor
64695
- };
64696
- },
64790
+ listTrackIdPage: async (cursor) => trackStore.queryRecentIds({
64791
+ deviceIds,
64792
+ ...input.since !== void 0 ? { since: input.since } : {},
64793
+ ...input.until !== void 0 ? { until: input.until } : {},
64794
+ ...cursor !== null ? { cursor } : {},
64795
+ limit: REBUILD_PAGE_SIZE
64796
+ }),
64697
64797
  loadSource: (trackId) => loadRebuildSource({
64698
64798
  media: mediaStore,
64699
64799
  events: eventStore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-post-analysis",
3
- "version": "1.2.126",
3
+ "version": "1.2.127",
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",