@camstack/addon-post-analysis 1.2.116 → 1.2.117

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.
@@ -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-C0uPGtBv.js");
5
+ const require_dist = require("../dist-Dk-BdVHw.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");
@@ -15149,26 +15149,28 @@ function remainingSummaryCandidates(kept, taken, rank) {
15149
15149
  /**
15150
15150
  * The media a tile is drawn from, best first.
15151
15151
  *
15152
- * For a group: `groupCrop` (union crop, the group's own blob) then
15153
- * `nativeFrame` (pointer at the anchor's clean keyFrame). A digest of a
15154
- * cohort may show the native still when the crop was not written; a
15155
- * member's `keyFrame` listed as itself stays off this ladder so tracker
15156
- * ID churn cannot refill a mosaic with eleven driveways. Boxed frames stay
15157
- * absent: a drawn rectangle is a permanent lie in a digest tile.
15152
+ * First-level group crop only (`groupCrop`), then the group's native-frame
15153
+ * pointer, then a member's clean full frame. Member `thumbnail` / plate /
15154
+ * face crops stay off this ladder: a successful plate read overwrites the
15155
+ * vehicle first-level crop under the same `thumbnail` kind, and a digest
15156
+ * tile of a targa is the wrong picture. Boxed frames stay absent: a drawn
15157
+ * rectangle is a permanent lie in a digest tile. Duplicate driveways from
15158
+ * tracker ID churn are collapsed by `frameDedupeKey`, not by hiding the
15159
+ * full-frame fallback.
15158
15160
  */
15159
15161
  var SUMMARY_MEDIA_LADDER = [
15160
15162
  "groupCrop",
15161
15163
  "nativeFrame",
15162
- "thumbnail",
15163
- "thumbnailSmall",
15164
- "snapshot"
15164
+ "keyFrame",
15165
+ "keyFrameSmall",
15166
+ "fullFrame"
15165
15167
  ];
15166
15168
  /**
15167
15169
  * Pick ONE media row for a track — the cheap question first (index rows only),
15168
15170
  * so exactly one blob is ever read.
15169
15171
  *
15170
- * Within a rung the row CLOSEST to the track's peak is chosen; `snapshot` is a
15171
- * filmstrip and its rows accumulate, so "the first one" would systematically
15172
+ * Within a rung the row CLOSEST to the track's peak is chosen; a member may
15173
+ * carry more than one full-frame still, so "the first one" would systematically
15172
15174
  * pick the moment the subject entered rather than the moment it was best seen.
15173
15175
  */
15174
15176
  function pickSummaryMedia(rows, aroundMs) {
@@ -39935,8 +39937,9 @@ var OverlayDetailStateStore = class {
39935
39937
  *
39936
39938
  * See docs/superpowers/specs/2026-07-17-package-zones-design.md §3.3 +
39937
39939
  * docs/superpowers/specs/2026-07-17-detection-config-exposure-design.md
39938
- * (surface C). Dwell default is 15s per operator decision (the design's
39939
- * §3.3 draft said 45s).
39940
+ * (surface C). Confirmation is N frames (default 3); dwell default is 0
39941
+ * (an extra bar). The 15 s dwell plus a 5 s round-gap missed a real
39942
+ * parcel on device 615 (2026-08-24 13:40).
39940
39943
  */
39941
39944
  var PackageDropSettingsSchema = require_dist.object({
39942
39945
  /**
@@ -39957,13 +39960,20 @@ var PackageDropSettingsSchema = require_dist.object({
39957
39960
  */
39958
39961
  packageDropEnabled: require_dist.boolean().default(true),
39959
39962
  /**
39960
- * Minimum OBSERVED dwell (seconds, since first-seen) before a newly
39961
- * promoted stationary package counts as a delivery. Kills a bag briefly
39962
- * set down and snatched back. Operator default 15s. Values beyond the
39963
- * stationary promotion window (30s) are satisfied only once the entry
39964
- * has actually been observed that long.
39963
+ * Frames the box must be re-seen on before it is a parked package.
39964
+ * Same bar as tracker `minHits`: one frame is a flicker, two can still
39965
+ * be a passer-by's bag, three is "it stayed". A settle burst of three
39966
+ * frames in ~1 s is enough; no second governor round after the courier
39967
+ * left. Device 615 2026-08-24 13:40 expired at 1 round / 1.5 s dwell.
39968
+ */
39969
+ packageDropMinFrames: require_dist.number().int().min(1).max(20).default(3),
39970
+ /**
39971
+ * Extra OBSERVED dwell (seconds, since first-seen) on top of N frames.
39972
+ * 0 (the default) means the frame count is enough — a courier who drops
39973
+ * and leaves in 2 s still delivers. Raise this to reject a bag briefly
39974
+ * set down and snatched back.
39965
39975
  */
39966
- packageDropDwellSec: require_dist.number().int().min(0).max(3600).default(15),
39976
+ packageDropDwellSec: require_dist.number().int().min(0).max(3600).default(0),
39967
39977
  /** Reject tiny far-field blobs: min bbox area as a fraction of the frame. */
39968
39978
  packageDropMinBboxAreaFrac: require_dist.number().min(0).max(1).default(.004),
39969
39979
  /**
@@ -39981,13 +39991,15 @@ var PackageDropSettingsSchema = require_dist.object({
39981
39991
  packageDropPickupEnabled: require_dist.boolean().default(true),
39982
39992
  /**
39983
39993
  * A parcel needs a deliverer: a stillness candidate only ever promotes to
39984
- * `delivered` if a non-package track (person/vehicle/animal) was active on
39985
- * the device within this many seconds BEFORE the box first appeared — and a
39986
- * departure only counts as `picked-up` with somebody around the
39987
- * disappearance. 0 disables both gates. Device 615's streetlight wedge
39988
- * cycled delivered→picked-up ~23×/night with nobody in frame, ever.
39989
- * Default 300 s: wide enough to absorb the motion-settle governor's cadence
39990
- * (the box is often first SEEN minutes after the courier left).
39994
+ * `delivered` if a non-package track (person/vehicle/animal) was active IN
39995
+ * A CONFIGURED PACKAGE DROP-OFF ZONE within this many seconds BEFORE the
39996
+ * box first appeared — and a departure only counts as `picked-up` with
39997
+ * somebody in that zone around the disappearance. Whole-camera motion (a
39998
+ * walker at the far gate) does not count. 0 disables both gates. Device
39999
+ * 615's streetlight wedge cycled delivered→picked-up ~23×/night with
40000
+ * nobody in the drop-off zone. Default 300 s: wide enough to absorb the
40001
+ * motion-settle governor's cadence (the box is often first SEEN minutes
40002
+ * after the courier left).
39991
40003
  */
39992
40004
  packageDropActivityWindowSec: require_dist.number().int().min(0).max(3600).default(300),
39993
40005
  /**
@@ -40011,6 +40023,7 @@ function resolvePackageDropSettings(raw) {
40011
40023
  };
40012
40024
  return {
40013
40025
  packageDropEnabled: pick("packageDropEnabled"),
40026
+ packageDropMinFrames: pick("packageDropMinFrames"),
40014
40027
  packageDropDwellSec: pick("packageDropDwellSec"),
40015
40028
  packageDropMinBboxAreaFrac: pick("packageDropMinBboxAreaFrac"),
40016
40029
  packageDropMaxBboxAreaFrac: pick("packageDropMaxBboxAreaFrac"),
@@ -40268,29 +40281,6 @@ var DeferredBirthRegistry = class {
40268
40281
  this.byDevice.delete(deviceKey);
40269
40282
  }
40270
40283
  };
40271
- var AnalyticsGroupRecordSchema = require_dist.object({
40272
- id: require_dist.string(),
40273
- deviceId: require_dist.number().int(),
40274
- openedAt: require_dist.number().int(),
40275
- closedAt: require_dist.number().int(),
40276
- timestamp: require_dist.number().int(),
40277
- memberCount: require_dist.number().int(),
40278
- memberTrackIds: require_dist.array(require_dist.string()).readonly(),
40279
- className: require_dist.string(),
40280
- classes: require_dist.array(require_dist.string()).readonly(),
40281
- /** Relative event-media path, or null when the group has no picture yet. */
40282
- mediaUrl: require_dist.string().nullable(),
40283
- singleton: require_dist.boolean()
40284
- });
40285
- var AnalyticsGroupMemberSchema = require_dist.object({
40286
- trackId: require_dist.string(),
40287
- deviceId: require_dist.number().int(),
40288
- className: require_dist.string(),
40289
- firstSeen: require_dist.number().int(),
40290
- lastSeen: require_dist.number().int(),
40291
- mediaUrl: require_dist.string().nullable()
40292
- });
40293
- var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: require_dist.array(AnalyticsGroupMemberSchema).readonly() });
40294
40284
  var ListGroupsInputSchema = require_dist.object({
40295
40285
  deviceId: require_dist.number().int().optional(),
40296
40286
  sinceMs: require_dist.number().int().optional(),
@@ -40300,66 +40290,11 @@ var ListGroupsInputSchema = require_dist.object({
40300
40290
  });
40301
40291
  var groupActions = require_dist.defineCustomActions({
40302
40292
  "analytics.listGroups": require_dist.customAction(ListGroupsInputSchema, require_dist.object({
40303
- groups: require_dist.array(AnalyticsGroupRecordSchema).readonly(),
40293
+ groups: require_dist.array(require_dist.AnalyticsGroupRecordSchema).readonly(),
40304
40294
  hasMore: require_dist.boolean()
40305
40295
  })),
40306
- "analytics.getGroup": require_dist.customAction(require_dist.object({ groupId: require_dist.string().min(1) }), require_dist.object({ group: AnalyticsGroupDetailSchema.nullable() }))
40296
+ "analytics.getGroup": require_dist.customAction(require_dist.object({ groupId: require_dist.string().min(1) }), require_dist.object({ group: require_dist.AnalyticsGroupDetailSchema.nullable() }))
40307
40297
  });
40308
- //#endregion
40309
- //#region src/pipeline-analytics/group/group-query.ts
40310
- /**
40311
- * Stamp a relative event-media URL.
40312
- *
40313
- * Media keys contain `:` and the data-plane serves them by key. Track ids are
40314
- * bare and fall through to the existing event/track thumbnail resolver. Never
40315
- * percent-encode: the handler does not URL-decode the path.
40316
- */
40317
- function stampEventMediaUrl(baseUrl, id) {
40318
- if (baseUrl === null || baseUrl.length === 0) return null;
40319
- if (id === null || id === void 0 || id.length === 0) return null;
40320
- return `${baseUrl}/${id}`;
40321
- }
40322
- /** Union of every class on the members; `className` is the first, stable order. */
40323
- function aggregateGroupClasses(tracks) {
40324
- const seen = /* @__PURE__ */ new Set();
40325
- const classes = [];
40326
- for (const track of tracks) for (const name of [track.className, ...track.classes ?? []]) {
40327
- if (name.length === 0 || seen.has(name)) continue;
40328
- seen.add(name);
40329
- classes.push(name);
40330
- }
40331
- return {
40332
- className: classes[0] ?? "unknown",
40333
- classes
40334
- };
40335
- }
40336
- function toGroupRecord(input) {
40337
- const { className, classes } = aggregateGroupClasses(input.tracks);
40338
- const mediaId = input.group.mediaKey ?? input.group.nativeKey ?? (input.group.memberCount === 1 ? input.memberTrackIds[0] : null);
40339
- return {
40340
- id: input.group.id,
40341
- deviceId: input.group.deviceId,
40342
- openedAt: input.group.openedAt,
40343
- closedAt: input.group.closedAt,
40344
- timestamp: input.group.timestamp,
40345
- memberCount: input.memberTrackIds.length > 0 ? input.memberTrackIds.length : input.group.memberCount,
40346
- memberTrackIds: [...input.memberTrackIds],
40347
- className,
40348
- classes: [...classes],
40349
- mediaUrl: stampEventMediaUrl(input.eventMediaBaseUrl, mediaId),
40350
- singleton: input.memberTrackIds.length <= 1
40351
- };
40352
- }
40353
- function toGroupMember(member, track, eventMediaBaseUrl) {
40354
- return {
40355
- trackId: member.trackId,
40356
- deviceId: member.deviceId,
40357
- className: track?.className ?? "unknown",
40358
- firstSeen: track?.firstSeen ?? member.timestamp,
40359
- lastSeen: track?.lastSeen ?? member.timestamp,
40360
- mediaUrl: stampEventMediaUrl(eventMediaBaseUrl, member.trackId)
40361
- };
40362
- }
40363
40298
  function trackToLite(track) {
40364
40299
  return {
40365
40300
  trackId: track.trackId,
@@ -40369,56 +40304,18 @@ function trackToLite(track) {
40369
40304
  lastSeen: track.lastSeen
40370
40305
  };
40371
40306
  }
40372
- async function listGroupRecords(ports, input) {
40373
- const page = await ports.groupStore.listRecent(input);
40374
- const groups = [];
40375
- for (const group of page.groups) groups.push(await buildGroupRecord(ports, group));
40376
- return {
40377
- groups,
40378
- hasMore: page.hasMore
40379
- };
40380
- }
40381
- async function getGroupDetail(ports, groupId) {
40382
- const group = await ports.groupStore.get(groupId);
40383
- if (group === null) return null;
40384
- const members = await ports.groupStore.listMembers(groupId);
40385
- const lites = [];
40386
- const rows = [];
40387
- for (const member of members) {
40388
- const track = await ports.loadTrack(member.trackId);
40389
- const lite = track === null ? null : trackToLite(track);
40390
- if (lite !== null) lites.push(lite);
40391
- rows.push(toGroupMember(member, lite, ports.eventMediaBaseUrl()));
40392
- }
40393
- return {
40394
- ...toGroupRecord({
40395
- group,
40396
- memberTrackIds: members.map((m) => m.trackId),
40397
- tracks: lites,
40398
- eventMediaBaseUrl: ports.eventMediaBaseUrl()
40399
- }),
40400
- members: rows
40401
- };
40402
- }
40403
- async function buildGroupRecord(ports, group) {
40404
- const members = await ports.groupStore.listMembers(group.id);
40405
- const lites = [];
40406
- for (const member of members) {
40407
- const track = await ports.loadTrack(member.trackId);
40408
- if (track !== null) lites.push(trackToLite(track));
40409
- }
40410
- return toGroupRecord({
40411
- group,
40412
- memberTrackIds: members.map((m) => m.trackId),
40413
- tracks: lites,
40414
- eventMediaBaseUrl: ports.eventMediaBaseUrl()
40415
- });
40416
- }
40417
40307
  /**
40418
40308
  * Gallery and digest read the same ladder: groupCrop, then the native-frame
40419
- * pointer, then member subject crops. A multi with no crop still has a
40420
- * picture if the sealer stored `nativeKey`.
40309
+ * pointer, then a member's clean full frame. Plate / face / member
40310
+ * thumbnails never reach the picker — a plate read overwrites the vehicle
40311
+ * first-level crop under the same `thumbnail` kind. A multi with no crop
40312
+ * still has a picture if the sealer stored `nativeKey`.
40421
40313
  */
40314
+ var DIGEST_MEMBER_FALLBACK_KINDS = new Set([
40315
+ "keyFrame",
40316
+ "keyFrameSmall",
40317
+ "fullFrame"
40318
+ ]);
40422
40319
  function digestMediaRows(input) {
40423
40320
  const owned = [];
40424
40321
  if (input.mediaKey !== null && input.mediaKey.length > 0) owned.push({
@@ -40432,7 +40329,7 @@ function digestMediaRows(input) {
40432
40329
  timestamp: input.timestamp
40433
40330
  });
40434
40331
  if (owned.length > 0) return owned;
40435
- return input.memberRows;
40332
+ return input.memberRows.filter((row) => DIGEST_MEMBER_FALLBACK_KINDS.has(row.kind));
40436
40333
  }
40437
40334
  var MATCH_WINDOW_MS = 1e3;
40438
40335
  var MOVE_EPS = .35;
@@ -40986,6 +40883,34 @@ function toInstant(t) {
40986
40883
  confidence: t.importance
40987
40884
  };
40988
40885
  }
40886
+ function memberBoxesAt(members, at, frame) {
40887
+ const w = Math.max(frame.width, 1);
40888
+ const h = Math.max(frame.height, 1);
40889
+ const boxes = [];
40890
+ for (const member of members) {
40891
+ let best = member.positions[0];
40892
+ let bestDt = Number.POSITIVE_INFINITY;
40893
+ for (const p of member.positions) {
40894
+ const dt = Math.abs(p.timestamp - at);
40895
+ if (dt <= 1e3 && dt < bestDt) {
40896
+ best = p;
40897
+ bestDt = dt;
40898
+ }
40899
+ }
40900
+ if (best === void 0 || bestDt === Number.POSITIVE_INFINITY) continue;
40901
+ boxes.push({
40902
+ bbox: {
40903
+ x: best.bbox.x / w,
40904
+ y: best.bbox.y / h,
40905
+ w: best.bbox.w / w,
40906
+ h: best.bbox.h / h
40907
+ },
40908
+ label: member.className,
40909
+ observedAt: best.timestamp
40910
+ });
40911
+ }
40912
+ return boxes;
40913
+ }
40989
40914
  function toSealer(track) {
40990
40915
  return {
40991
40916
  trackId: track.trackId,
@@ -41002,7 +40927,11 @@ var GroupSealer = class {
41002
40927
  constructor(deps) {
41003
40928
  this.deps = deps;
41004
40929
  }
40930
+ get processEnabled() {
40931
+ return this.deps.enabled ?? false;
40932
+ }
41005
40933
  async flushIdle(buffer, now = this.deps.now()) {
40934
+ if (!this.processEnabled) return 0;
41006
40935
  let written = 0;
41007
40936
  for (const group of buffer.idleGroups(now, GROUP_SESSION_IDLE_MS)) if (await this.persistLiveGroup(group, buffer)) {
41008
40937
  buffer.drop([group.id]);
@@ -41015,6 +40944,7 @@ var GroupSealer = class {
41015
40944
  * into the live buffer, persist whoever is already idle, keep the rest in RAM.
41016
40945
  */
41017
40946
  async recoverCamera(deviceId, buffer) {
40947
+ if (!this.processEnabled) return 0;
41018
40948
  if (buffer.isPrimed(deviceId)) return 0;
41019
40949
  const now = this.deps.now();
41020
40950
  try {
@@ -41043,6 +40973,7 @@ var GroupSealer = class {
41043
40973
  * is what wedged hub-main.
41044
40974
  */
41045
40975
  async sealWindow(deviceId, since, until) {
40976
+ if (!this.processEnabled) return 0;
41046
40977
  try {
41047
40978
  const closed = await this.deps.trackStore.queryHistorical({
41048
40979
  deviceId,
@@ -41157,26 +41088,54 @@ var GroupSealer = class {
41157
41088
  return;
41158
41089
  }
41159
41090
  try {
41160
- const raw = await (0, sharp.default)(source.jpeg).removeAlpha().raw().toBuffer({ resolveWithObject: true });
41161
- if (raw.info.channels !== 3) return;
41162
- const union = {
41163
- x: instant.ux * raw.info.width,
41164
- y: instant.uy * raw.info.height,
41165
- w: instant.uw * raw.info.width,
41166
- h: instant.uh * raw.info.height
41167
- };
41168
- const crop = await cropGroupFromRgb(raw.data, raw.info.width, raw.info.height, union);
41169
- const mediaKey = await this.deps.mediaStore.put({
41091
+ const raw = await (0, sharp.default)(source.jpeg).removeAlpha().toColorspace("srgb").raw().toBuffer({ resolveWithObject: true });
41092
+ if (raw.info.channels === 3) {
41093
+ const union = {
41094
+ x: instant.ux * raw.info.width,
41095
+ y: instant.uy * raw.info.height,
41096
+ w: instant.uw * raw.info.width,
41097
+ h: instant.uh * raw.info.height
41098
+ };
41099
+ const crop = await cropGroupFromRgb(raw.data, raw.info.width, raw.info.height, union);
41100
+ const mediaKey = await this.deps.mediaStore.put({
41101
+ deviceId,
41102
+ ownerKind: "group",
41103
+ ownerId: draft.id,
41104
+ kind: "thumbnail",
41105
+ timestamp,
41106
+ data: crop
41107
+ });
41108
+ await this.deps.groupStore.setMediaKey(draft.id, mediaKey);
41109
+ }
41110
+ } catch (err) {
41111
+ this.deps.logger.warn("group sealer: crop failed", {
41112
+ tags: { deviceId },
41113
+ meta: {
41114
+ groupId: draft.id,
41115
+ error: String(err)
41116
+ }
41117
+ });
41118
+ }
41119
+ try {
41120
+ const composed = await composeGroupFrame({
41121
+ jpeg: source.jpeg,
41122
+ boxes: memberBoxesAt(instantMembers, timestamp, frame),
41123
+ at: timestamp,
41124
+ logger: this.deps.logger,
41125
+ deviceId,
41126
+ groupKey: draft.id
41127
+ });
41128
+ if (composed === null) return;
41129
+ await this.deps.mediaStore.put({
41170
41130
  deviceId,
41171
41131
  ownerKind: "group",
41172
41132
  ownerId: draft.id,
41173
- kind: "thumbnail",
41133
+ kind: "fullFrameBoxed",
41174
41134
  timestamp,
41175
- data: crop
41135
+ data: composed.bytes
41176
41136
  });
41177
- await this.deps.groupStore.setMediaKey(draft.id, mediaKey);
41178
41137
  } catch (err) {
41179
- this.deps.logger.warn("group sealer: crop failed", {
41138
+ this.deps.logger.warn("group sealer: boxed frame failed", {
41180
41139
  tags: { deviceId },
41181
41140
  meta: {
41182
41141
  groupId: draft.id,
@@ -41201,6 +41160,37 @@ var GroupSealer = class {
41201
41160
  }
41202
41161
  };
41203
41162
  //#endregion
41163
+ //#region src/pipeline-analytics/group/group-cursor.ts
41164
+ function encodeGroupCursor(cursor) {
41165
+ return Buffer.from(JSON.stringify({
41166
+ c: cursor.closedAt,
41167
+ i: cursor.groupId
41168
+ }), "utf8").toString("base64url");
41169
+ }
41170
+ function decodeGroupCursor(raw) {
41171
+ let parsed;
41172
+ try {
41173
+ parsed = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
41174
+ } catch {
41175
+ throw new Error("listGroups: malformed cursor");
41176
+ }
41177
+ if (parsed === null || typeof parsed !== "object" || !("c" in parsed) || !("i" in parsed) || typeof parsed.c !== "number" || !Number.isFinite(parsed.c) || typeof parsed.i !== "string" || parsed.i.length === 0) throw new Error("listGroups: malformed cursor");
41178
+ return {
41179
+ closedAt: parsed.c,
41180
+ groupId: parsed.i
41181
+ };
41182
+ }
41183
+ function compareGroupDesc(a, b) {
41184
+ if (a.closedAt !== b.closedAt) return b.closedAt - a.closedAt;
41185
+ if (a.id === b.id) return 0;
41186
+ return a.id < b.id ? 1 : -1;
41187
+ }
41188
+ /** True when `row` sits strictly AFTER the cursor in DESC order. */
41189
+ function isAfterGroupCursor(row, cursor) {
41190
+ if (row.closedAt < cursor.closedAt) return true;
41191
+ return row.closedAt === cursor.closedAt && row.id < cursor.groupId;
41192
+ }
41193
+ //#endregion
41204
41194
  //#region src/pipeline-analytics/group/group-store.ts
41205
41195
  /**
41206
41196
  * The group ENTITY store — a co-moving partition of one session, with one
@@ -41462,6 +41452,24 @@ var TrackGroupStore = class {
41462
41452
  data: r.data
41463
41453
  }));
41464
41454
  }
41455
+ /**
41456
+ * Membership for a page of groups in ONE query. `listGroupRecords` used to
41457
+ * call {@link listMembers} per row — 60 groups is 60 settings-store RPCs,
41458
+ * ~20 s, and the viewer feed times out into a retry loop.
41459
+ */
41460
+ async listMembersForGroups(groupIds) {
41461
+ if (groupIds.length === 0) return [];
41462
+ return (await this.store.query.query({
41463
+ collection: TRACK_GROUP_MEMBERS_COLLECTION,
41464
+ filter: {
41465
+ whereIn: { groupId: [...groupIds] },
41466
+ limit: 1e4
41467
+ }
41468
+ })).map((r) => this.normalizeMember({
41469
+ id: r.id,
41470
+ data: r.data
41471
+ }));
41472
+ }
41465
41473
  async membersOfTracks(trackIds) {
41466
41474
  const out = [];
41467
41475
  for (const trackId of trackIds) {
@@ -41477,20 +41485,29 @@ var TrackGroupStore = class {
41477
41485
  return out;
41478
41486
  }
41479
41487
  /**
41480
- * Newest-closed page for the Library Groups tab.
41488
+ * Newest-closed page for the Groups feed.
41481
41489
  *
41482
41490
  * Time filter is overlap (`closedAt >= since` and `openedAt <= until`),
41483
41491
  * same as {@link listOverlapping}. Pagination is applied AFTER the window
41484
41492
  * so a day cursor is honest rather than a raw-row offset that skips live
41485
- * groups on a busy camera.
41493
+ * groups on a busy camera. `deviceIds: []` is an empty page (same as
41494
+ * `listRecentTracks`); omit the field for every camera. `deviceId` stays
41495
+ * as the single-camera alias the custom-action bridge still sends.
41486
41496
  */
41487
41497
  async listRecent(input) {
41488
41498
  const limit = Math.min(Math.max(input.limit ?? 40, 1), 100);
41489
41499
  const offset = Math.max(input.offset ?? 0, 0);
41490
- const slice = (await this.store.query.query({
41500
+ const deviceIds = input.deviceIds !== void 0 ? input.deviceIds : input.deviceId !== void 0 ? [input.deviceId] : void 0;
41501
+ if (deviceIds !== void 0 && deviceIds.length === 0) return {
41502
+ groups: [],
41503
+ hasMore: false,
41504
+ nextCursor: null
41505
+ };
41506
+ const cursor = input.cursor !== void 0 && input.cursor.length > 0 ? decodeGroupCursor(input.cursor) : null;
41507
+ const filtered = (await this.store.query.query({
41491
41508
  collection: TRACK_GROUPS_COLLECTION,
41492
41509
  filter: {
41493
- ...input.deviceId !== void 0 ? { where: { deviceId: input.deviceId } } : {},
41510
+ ...deviceIds !== void 0 && deviceIds.length === 1 ? { where: { deviceId: deviceIds[0] } } : deviceIds !== void 0 ? { whereIn: { deviceId: [...deviceIds] } } : {},
41494
41511
  orderBy: {
41495
41512
  field: "closedAt",
41496
41513
  direction: "desc"
@@ -41503,11 +41520,20 @@ var TrackGroupStore = class {
41503
41520
  })).filter((g) => {
41504
41521
  if (input.sinceMs !== void 0 && g.closedAt < input.sinceMs) return false;
41505
41522
  if (input.untilMs !== void 0 && g.openedAt > input.untilMs) return false;
41523
+ if (cursor !== null && !isAfterGroupCursor(g, cursor)) return false;
41506
41524
  return true;
41507
- }).slice(offset, offset + limit + 1);
41525
+ }).toSorted(compareGroupDesc);
41526
+ const slice = (cursor !== null ? filtered : filtered.slice(offset)).slice(0, limit + 1);
41527
+ const groups = slice.slice(0, limit);
41528
+ const hasMore = slice.length > limit;
41529
+ const last = groups[groups.length - 1];
41508
41530
  return {
41509
- groups: slice.slice(0, limit),
41510
- hasMore: slice.length > limit
41531
+ groups,
41532
+ hasMore,
41533
+ nextCursor: hasMore && last !== void 0 ? encodeGroupCursor({
41534
+ closedAt: last.closedAt,
41535
+ groupId: last.id
41536
+ }) : null
41511
41537
  };
41512
41538
  }
41513
41539
  /** Groups whose life overlaps `[since, until]` on one camera. */
@@ -42839,7 +42865,7 @@ function evaluateStationaryPromotion(input) {
42839
42865
  };
42840
42866
  }
42841
42867
  var DEFAULT_PACKAGE_STILLNESS_CONFIG = {
42842
- minRounds: 2,
42868
+ minHits: 3,
42843
42869
  roundGapMs: 5e3,
42844
42870
  ttlMs: 5 * 6e4,
42845
42871
  assocIou: .3
@@ -42872,8 +42898,9 @@ var PackageStillnessTracker = class {
42872
42898
  const prev = next[index];
42873
42899
  if (prev === void 0) continue;
42874
42900
  consumed.add(prev.id);
42875
- const updated = confirm(prev, input, s, this.config.roundGapMs);
42876
- if (!(updated.promotedAt === null && updated.activityBackedAtOpen && updated.rounds >= this.config.minRounds && input.timestamp - updated.firstSeenAt >= input.minDwellMs)) {
42901
+ const updated = confirm(prev, input, s);
42902
+ const minHits = input.minHits ?? this.config.minHits;
42903
+ if (!(updated.promotedAt === null && updated.activityBackedAtOpen && updated.hits >= minHits && input.timestamp - updated.firstSeenAt >= input.minDwellMs)) {
42877
42904
  next[index] = updated;
42878
42905
  continue;
42879
42906
  }
@@ -42897,7 +42924,7 @@ var PackageStillnessTracker = class {
42897
42924
  id: c.id,
42898
42925
  className: c.className,
42899
42926
  bbox: { ...c.bbox },
42900
- rounds: c.rounds,
42927
+ hits: c.hits,
42901
42928
  firstSeenAt: c.firstSeenAt,
42902
42929
  lastSeenAt: c.lastSeenAt,
42903
42930
  activityBackedAtOpen: c.activityBackedAtOpen
@@ -42955,7 +42982,7 @@ var PackageStillnessTracker = class {
42955
42982
  frameHeight: input.frameHeight,
42956
42983
  firstSeenAt: input.timestamp,
42957
42984
  lastSeenAt: input.timestamp,
42958
- rounds: 1,
42985
+ hits: 1,
42959
42986
  promotedAt: null,
42960
42987
  activityBackedAtOpen,
42961
42988
  ...s.trackId !== void 0 ? { sourceTrackId: s.trackId } : {},
@@ -42965,18 +42992,16 @@ var PackageStillnessTracker = class {
42965
42992
  };
42966
42993
  }
42967
42994
  };
42968
- /** Re-confirm a candidate with this frame's box. A new ROUND is only counted
42969
- * when the gap since the last sighting clears `roundGapMs`, so a burst of
42970
- * frames inside one governor round counts once. */
42971
- function confirm(prev, input, s, roundGapMs) {
42972
- const newRound = input.timestamp - prev.lastSeenAt >= roundGapMs;
42995
+ /** Re-confirm a candidate with this frame's box. Every sighting is a hit —
42996
+ * a settle burst of three frames in one second is three confirmations. */
42997
+ function confirm(prev, input, s) {
42973
42998
  return {
42974
42999
  ...prev,
42975
43000
  bbox: { ...s.bbox },
42976
43001
  frameWidth: input.frameWidth,
42977
43002
  frameHeight: input.frameHeight,
42978
43003
  lastSeenAt: input.timestamp,
42979
- rounds: newRound ? prev.rounds + 1 : prev.rounds,
43004
+ hits: prev.hits + 1,
42980
43005
  ...s.trackId !== void 0 ? { sourceTrackId: s.trackId } : {},
42981
43006
  ...s.label !== void 0 ? { label: s.label } : {},
42982
43007
  ...s.confidence !== void 0 ? { confidence: s.confidence } : {},
@@ -43001,6 +43026,31 @@ function toEntry(c) {
43001
43026
  };
43002
43027
  }
43003
43028
  //#endregion
43029
+ //#region src/pipeline-analytics/pipeline/package/package-zone-activity.ts
43030
+ /**
43031
+ * Whether a live track is motion inside a configured package drop-off zone.
43032
+ *
43033
+ * The stillness activity gate used to stamp ANY non-package track on the
43034
+ * camera. A walker at the far gate then "delivered" the streetlight wedge
43035
+ * on the doormat. The operator's prerequisite is motion in the drop-off
43036
+ * zone the package rule points at — the same zones `PackageDropDetector`
43037
+ * uses for the centroid gate.
43038
+ */
43039
+ function isDelivererInPackageZone(trackZones, packageZoneIds) {
43040
+ if (packageZoneIds.size === 0 || trackZones.length === 0) return false;
43041
+ for (const z of trackZones) if (packageZoneIds.has(z)) return true;
43042
+ return false;
43043
+ }
43044
+ /** Zone ids referenced by enabled `package`-stage rules. Disabled rules do not count. */
43045
+ function collectPackageZoneIds(rules) {
43046
+ const ids = /* @__PURE__ */ new Set();
43047
+ for (const r of rules) {
43048
+ if (r.enabled === false) continue;
43049
+ for (const z of r.zoneIds) ids.add(z);
43050
+ }
43051
+ return ids;
43052
+ }
43053
+ //#endregion
43004
43054
  //#region src/pipeline-analytics/pipeline/stationary/stationary-zones.ts
43005
43055
  /**
43006
43056
  * Zone ids whose 0–1 polygon contains the entry's normalised bbox centroid.
@@ -49904,6 +49954,28 @@ var TrackStore = class {
49904
49954
  return this.rowToTrack(row.id, row.data);
49905
49955
  }
49906
49956
  /**
49957
+ * Batch read of persisted tracks. `listGroupRecords` cannot call
49958
+ * {@link getPersistedByTrackId} per member — that is one settings-store RPC
49959
+ * per row and a 60-group page takes ~20 s, which the viewer feed times out.
49960
+ */
49961
+ async getPersistedByTrackIds(trackIds) {
49962
+ if (trackIds.length === 0) return [];
49963
+ const PAGE = 500;
49964
+ const out = [];
49965
+ for (let i = 0; i < trackIds.length; i += PAGE) {
49966
+ const chunk = trackIds.slice(i, i + PAGE);
49967
+ const rows = await this.store.query.query({
49968
+ collection: TRACKS_COLLECTION,
49969
+ filter: {
49970
+ whereIn: { id: [...chunk] },
49971
+ limit: chunk.length
49972
+ }
49973
+ });
49974
+ for (const row of rows) out.push(this.rowToTrack(row.id, row.data));
49975
+ }
49976
+ return out;
49977
+ }
49978
+ /**
49907
49979
  * Persist a SYNTHETIC track (a sensor/control event projected into the
49908
49980
  * unified track store). Unlike `persistCompleted` there is no live in-RAM
49909
49981
  * state and no trajectory envelope (synthetic tracks carry no positions) —
@@ -54976,11 +55048,22 @@ function buildGlobalSettingsSchema(cap) {
54976
55048
  description: "Also emit a package-picked-up event when the delivered package leaves.",
54977
55049
  default: PACKAGE_DROP_DEFAULTS.packageDropPickupEnabled
54978
55050
  },
55051
+ {
55052
+ type: "slider",
55053
+ key: "packageDropMinFrames",
55054
+ label: "Confirmation frames",
55055
+ description: "How many frames the box must be seen on before it counts as a delivery. One is a flicker; two can still be a passer-by; three is the default bar against false positives. Independent of dwell.",
55056
+ min: 1,
55057
+ max: 10,
55058
+ step: 1,
55059
+ default: PACKAGE_DROP_DEFAULTS.packageDropMinFrames,
55060
+ showValue: true
55061
+ },
54979
55062
  {
54980
55063
  type: "slider",
54981
55064
  key: "packageDropDwellSec",
54982
55065
  label: "Minimum dwell",
54983
- description: "How long a package must stay parked before it counts as a delivery. Higher = fewer false positives from bags briefly set down.",
55066
+ description: "Extra time the package must stay parked after the confirmation frames. 0 (default) means N frames is enough — a courier who drops and leaves in a couple of seconds still delivers. Raise to reject a bag briefly set down.",
54984
55067
  min: 0,
54985
55068
  max: 300,
54986
55069
  step: 5,
@@ -55014,7 +55097,7 @@ function buildGlobalSettingsSchema(cap) {
55014
55097
  type: "slider",
55015
55098
  key: "packageDropActivityWindowSec",
55016
55099
  label: "Deliverer window",
55017
- description: "A delivery only counts if a person or vehicle was active on this camera within this many seconds before the package appeared — and a pick-up only with somebody around when it vanished. 0 disables the requirement.",
55100
+ description: "A delivery only counts if a person or vehicle was in the configured package drop-off zone within this many seconds before the package appeared — and a pick-up only with somebody in that zone when it vanished. Motion elsewhere on the camera does not count. 0 disables the requirement.",
55018
55101
  min: 0,
55019
55102
  max: 1800,
55020
55103
  step: 30,
@@ -57439,11 +57522,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
57439
57522
  */
57440
57523
  packageStillness = new PackageStillnessTracker({ newId: () => (0, node_crypto.randomUUID)() });
57441
57524
  /**
57442
- * Frame-clock timestamp of the last NON-package track seen per device — the
57443
- * activity mirror behind the package activity gate ("a parcel needs a
57444
- * deliverer"). Deliberately not {@link lastTrackActivityMs}: that map is
57445
- * stamped by ANY live track, including the package-class FP itself, which
57446
- * would let the streetlight wedge vouch for its own delivery.
57525
+ * Frame-clock timestamp of the last NON-package track seen IN A PACKAGE
57526
+ * DROP-OFF ZONE per device — the activity mirror behind the package
57527
+ * activity gate ("a parcel needs a deliverer"). A walker at the far gate
57528
+ * does not stamp this. Deliberately not {@link lastTrackActivityMs}: that
57529
+ * map is stamped by ANY live track, including the package-class FP itself,
57530
+ * which would let the streetlight wedge vouch for its own delivery.
57447
57531
  */
57448
57532
  lastNonPackageActivityAt = /* @__PURE__ */ new Map();
57449
57533
  /** Last time the oversize-sighting drop was logged per device — one line
@@ -58810,13 +58894,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
58810
58894
  buildSummaryPorts(stores) {
58811
58895
  return {
58812
58896
  listTracks: async ({ deviceId, sinceMs, untilMs, limit }) => {
58813
- await this.groupSealer?.sealWindow(deviceId, sinceMs, untilMs);
58814
- const groups = await stores.groupStore.listOverlapping({
58815
- deviceId,
58816
- sinceMs,
58817
- untilMs,
58818
- limit
58819
- });
58820
58897
  const windowTracks = await stores.trackStore.queryHistorical({
58821
58898
  deviceId,
58822
58899
  since: sinceMs,
@@ -58824,33 +58901,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
58824
58901
  projection: "slim",
58825
58902
  limit: Math.max(limit, 2e3)
58826
58903
  });
58827
- const trackById = new Map(windowTracks.map((t) => [t.trackId, t]));
58828
58904
  const { minHits } = await this.resolveDeviceDetectionSensitivitySettings(deviceId);
58829
- const out = [];
58830
- for (const group of groups) {
58831
- const members = await stores.groupStore.listMembers(group.id);
58832
- const tracks = members.map((m) => trackById.get(m.trackId)).filter((t) => t !== void 0);
58833
- if (tracks.length === 0) continue;
58834
- const classes = [...new Set(tracks.flatMap((t) => [t.className, ...t.classes ?? []]))];
58835
- const zones = [...new Set(tracks.flatMap((t) => [...t.zonesVisited]))];
58836
- const importance = Math.max(0, ...tracks.map((t) => t.importance ?? 0));
58837
- const primary = tracks.reduce((best, t) => (t.importance ?? 0) > (best.importance ?? 0) ? t : best);
58838
- out.push({
58839
- trackId: group.id,
58840
- groupId: group.id,
58841
- memberTrackIds: members.map((m) => m.trackId),
58842
- deviceId,
58843
- className: primary.className,
58844
- firstSeen: group.openedAt,
58845
- lastSeen: group.closedAt,
58846
- importance,
58847
- zones,
58848
- classes,
58849
- ...tracks.every((t) => t.closedReason === "stationary-promotion") ? { stationary: true } : {},
58850
- confirmed: tracks.some((t) => t.observations !== void 0 && t.observations >= minHits)
58851
- });
58852
- }
58853
- return out;
58905
+ return windowTracks.map((t) => ({
58906
+ trackId: t.trackId,
58907
+ deviceId,
58908
+ className: t.className,
58909
+ firstSeen: t.firstSeen,
58910
+ lastSeen: t.lastSeen,
58911
+ zones: [...t.zonesVisited],
58912
+ ...t.importance !== void 0 ? { importance: t.importance } : {},
58913
+ ...t.classes !== void 0 && t.classes.length > 0 ? { classes: t.classes } : {},
58914
+ ...t.closedReason === "stationary-promotion" ? { stationary: true } : {},
58915
+ confirmed: t.observations !== void 0 && t.observations >= minHits
58916
+ }));
58854
58917
  },
58855
58918
  listMediaInfo: async (id) => {
58856
58919
  const group = await stores.groupStore.get(id);
@@ -60333,6 +60396,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
60333
60396
  deviceId,
60334
60397
  tracked: result.tracked,
60335
60398
  packageClasses,
60399
+ packageZoneIds: collectPackageZoneIds(proxy?.state.zoneRules.value?.package ?? []),
60336
60400
  settings: packageSettings,
60337
60401
  frameWidth: result.frameWidth,
60338
60402
  frameHeight: result.frameHeight,
@@ -62047,25 +62111,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
62047
62111
  }
62048
62112
  /** Hub-only: assign this sighting in RAM. First activity on a camera
62049
62113
  * replays 2 minutes of that camera (crash recover), then observe is sync. */
62050
- enqueueLiveGroup(track, now) {
62051
- if (!this.isPostProcessingNode || !this.groupLive) return;
62052
- const deviceId = track.deviceId;
62053
- const snapshot = toGroupableTrack(track);
62054
- if (this.groupLive.isPrimed(deviceId)) {
62055
- this.groupLive.observe(snapshot, now);
62056
- return;
62057
- }
62058
- const next = (this.groupObserveTail.get(deviceId) ?? Promise.resolve()).then(async () => {
62059
- if (this.groupLive && this.groupSealer && !this.groupLive.isPrimed(deviceId)) await this.groupSealer.recoverCamera(deviceId, this.groupLive);
62060
- this.groupLive?.observe(snapshot, now);
62061
- }).catch((err) => {
62062
- this.ctx.logger.warn("group live observe failed", {
62063
- tags: { deviceId },
62064
- meta: { error: String(err) }
62065
- });
62066
- });
62067
- this.groupObserveTail.set(deviceId, next);
62068
- }
62114
+ enqueueLiveGroup(track, now) {}
62069
62115
  /** Unified retention (Phase 1): earliest retained footage per device — the
62070
62116
  * follow-recordings cutoff source. Cached 10 min: availability answers in
62071
62117
  * ~70 ms but the sweep asks once per device per pass. `null` = no footage
@@ -62511,7 +62557,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
62511
62557
  runPackageStillness(input) {
62512
62558
  if (this.packageDropDetector === null) return;
62513
62559
  const tags = { deviceId: input.deviceId };
62514
- for (const t of input.tracked) if (!input.packageClasses.has(t.className)) {
62560
+ for (const t of input.tracked) {
62561
+ if (input.packageClasses.has(t.className)) continue;
62562
+ if (!isDelivererInPackageZone(t.zones, input.packageZoneIds)) continue;
62515
62563
  this.lastNonPackageActivityAt.set(input.deviceId, input.timestamp);
62516
62564
  break;
62517
62565
  }
@@ -62558,6 +62606,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
62558
62606
  frameWidth: input.frameWidth,
62559
62607
  frameHeight: input.frameHeight,
62560
62608
  timestamp: input.timestamp,
62609
+ minHits: input.settings.packageDropMinFrames,
62561
62610
  minDwellMs: input.settings.packageDropDwellSec * 1e3,
62562
62611
  activityWindowMs: input.settings.packageDropActivityWindowSec * 1e3,
62563
62612
  ...lastActivityAt !== void 0 ? { lastActivityAt } : {}
@@ -62661,9 +62710,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
62661
62710
  for (const ex of outcome.expired) {
62662
62711
  const observedDwellMs = ex.lastSeenAt - ex.firstSeenAt;
62663
62712
  const requiredDwellMs = input.settings.packageDropDwellSec * 1e3;
62713
+ const requiredHits = input.settings.packageDropMinFrames;
62664
62714
  const reasons = [];
62665
62715
  if (!ex.activityBackedAtOpen) reasons.push("no-activity-before-open");
62666
- if (ex.rounds < 2) reasons.push("rounds");
62716
+ if (ex.hits < requiredHits) reasons.push("hits");
62667
62717
  if (observedDwellMs < requiredDwellMs) reasons.push("dwell");
62668
62718
  this.ctx.logger.info("package candidate expired without promotion", {
62669
62719
  tags,
@@ -62671,8 +62721,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
62671
62721
  candidateId: ex.id,
62672
62722
  className: ex.className,
62673
62723
  bbox: ex.bbox,
62674
- rounds: ex.rounds,
62675
- minRounds: 2,
62724
+ hits: ex.hits,
62725
+ minHits: requiredHits,
62676
62726
  observedDwellMs,
62677
62727
  requiredDwellMs,
62678
62728
  activityBackedAtOpen: ex.activityBackedAtOpen,
@@ -62934,6 +62984,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
62934
62984
  async listRecentTracks(input) {
62935
62985
  return this.queryFacade.listRecentTracks(input);
62936
62986
  }
62987
+ async listGroups(input) {
62988
+ return {
62989
+ groups: [],
62990
+ nextCursor: null
62991
+ };
62992
+ }
62993
+ async getGroup(input) {
62994
+ return null;
62995
+ }
62937
62996
  /**
62938
62997
  * Every event kind the device can produce: built-ins (motion + audio),
62939
62998
  * detection classes actually observed on the device, and sensor kinds
@@ -64078,33 +64137,38 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
64078
64137
  buildFaceRescoreActionHandlers() {
64079
64138
  return { "face.rescoreTrack": (input) => this.rescoreFaceForTrack(input) };
64080
64139
  }
64081
- buildGroupActionHandlers() {
64082
- const ports = () => {
64083
- const groupStore = this.groupStore;
64084
- if (groupStore === null) return null;
64085
- return {
64086
- groupStore,
64087
- loadTrack: async (trackId) => {
64088
- const tracks = this.trackStore;
64089
- if (!tracks) return null;
64090
- return tracks.getActiveByTrack(trackId) ?? await tracks.getPersistedByTrackId(trackId);
64091
- },
64092
- eventMediaBaseUrl: () => this.eventMediaBaseUrl
64093
- };
64140
+ groupQueryPorts() {
64141
+ const groupStore = this.groupStore;
64142
+ if (groupStore === null) return null;
64143
+ return {
64144
+ groupStore,
64145
+ loadTracks: async (trackIds) => {
64146
+ const map = /* @__PURE__ */ new Map();
64147
+ const tracks = this.trackStore;
64148
+ if (!tracks || trackIds.length === 0) return map;
64149
+ const missing = [];
64150
+ for (const id of trackIds) {
64151
+ const active = tracks.getActiveByTrack(id);
64152
+ if (active !== null) map.set(id, trackToLite(active));
64153
+ else missing.push(id);
64154
+ }
64155
+ if (missing.length === 0) return map;
64156
+ for (const persisted of await tracks.getPersistedByTrackIds(missing)) map.set(persisted.trackId, trackToLite(persisted));
64157
+ return map;
64158
+ },
64159
+ eventMediaBaseUrl: () => this.eventMediaBaseUrl
64094
64160
  };
64161
+ }
64162
+ buildGroupActionHandlers() {
64095
64163
  return {
64096
64164
  "analytics.listGroups": async (input) => {
64097
- const ready = ports();
64098
- if (ready === null) return {
64165
+ return {
64099
64166
  groups: [],
64100
64167
  hasMore: false
64101
64168
  };
64102
- return listGroupRecords(ready, input);
64103
64169
  },
64104
64170
  "analytics.getGroup": async (input) => {
64105
- const ready = ports();
64106
- if (ready === null) return { group: null };
64107
- return { group: await getGroupDetail(ready, input.groupId) };
64171
+ return { group: null };
64108
64172
  }
64109
64173
  };
64110
64174
  }