@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.
- package/dist/{dist-C0uPGtBv.js → dist-Dk-BdVHw.js} +98 -1
- package/dist/{dist-B13sFjR2.mjs → dist-PMOtjzh2.mjs} +87 -2
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-k_ZbrH_x.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BmR_bLi_.mjs} +2 -2
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-m0Jqwk3C.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DlTk9-4b.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-DckJUjuL.mjs → hostInit-Ddl2vUu6.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +333 -269
- package/dist/pipeline-analytics/index.mjs +331 -267
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -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-
|
|
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
|
-
*
|
|
15153
|
-
*
|
|
15154
|
-
*
|
|
15155
|
-
*
|
|
15156
|
-
*
|
|
15157
|
-
*
|
|
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
|
-
"
|
|
15163
|
-
"
|
|
15164
|
-
"
|
|
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;
|
|
15171
|
-
*
|
|
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).
|
|
39939
|
-
*
|
|
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
|
-
*
|
|
39961
|
-
*
|
|
39962
|
-
*
|
|
39963
|
-
*
|
|
39964
|
-
*
|
|
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(
|
|
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
|
|
39985
|
-
*
|
|
39986
|
-
* departure only counts as `picked-up` with
|
|
39987
|
-
*
|
|
39988
|
-
*
|
|
39989
|
-
*
|
|
39990
|
-
*
|
|
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
|
|
40420
|
-
*
|
|
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
|
|
41162
|
-
|
|
41163
|
-
|
|
41164
|
-
|
|
41165
|
-
|
|
41166
|
-
|
|
41167
|
-
|
|
41168
|
-
|
|
41169
|
-
|
|
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: "
|
|
41133
|
+
kind: "fullFrameBoxed",
|
|
41174
41134
|
timestamp,
|
|
41175
|
-
data:
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
...
|
|
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
|
-
}).
|
|
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
|
|
41510
|
-
hasMore
|
|
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
|
-
|
|
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
|
|
42876
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
42969
|
-
*
|
|
42970
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
|
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
|
|
57443
|
-
*
|
|
57444
|
-
* deliverer").
|
|
57445
|
-
*
|
|
57446
|
-
*
|
|
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
|
-
|
|
58830
|
-
|
|
58831
|
-
|
|
58832
|
-
|
|
58833
|
-
|
|
58834
|
-
|
|
58835
|
-
|
|
58836
|
-
|
|
58837
|
-
|
|
58838
|
-
|
|
58839
|
-
|
|
58840
|
-
|
|
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)
|
|
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.
|
|
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
|
-
|
|
62675
|
-
|
|
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
|
-
|
|
64082
|
-
const
|
|
64083
|
-
|
|
64084
|
-
|
|
64085
|
-
|
|
64086
|
-
|
|
64087
|
-
|
|
64088
|
-
|
|
64089
|
-
|
|
64090
|
-
|
|
64091
|
-
|
|
64092
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|