@camstack/addon-post-analysis 1.2.116 → 1.2.118
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-CO6pe7bi.js} +104 -2
- package/dist/{dist-B13sFjR2.mjs → dist-Gn2fFMov.mjs} +93 -3
- 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-BYzwylnw.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-TlKH3NQ8.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +603 -355
- package/dist/pipeline-analytics/index.mjs +601 -353
- 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-CO6pe7bi.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) {
|
|
@@ -25922,7 +25924,7 @@ var CLASS_RANK_VEHICLE = .8;
|
|
|
25922
25924
|
var CLASS_RANK_ANIMAL = .5;
|
|
25923
25925
|
var CLASS_RANK_DEFAULT = .25;
|
|
25924
25926
|
var PERSON_CLASSES = new Set(["person", "face"]);
|
|
25925
|
-
var VEHICLE_CLASSES = new Set([
|
|
25927
|
+
var VEHICLE_CLASSES$1 = new Set([
|
|
25926
25928
|
"vehicle",
|
|
25927
25929
|
"car",
|
|
25928
25930
|
"truck",
|
|
@@ -25953,7 +25955,7 @@ function clamp01$1(x) {
|
|
|
25953
25955
|
function classRank(className) {
|
|
25954
25956
|
const c = className.toLowerCase();
|
|
25955
25957
|
if (PERSON_CLASSES.has(c)) return 1;
|
|
25956
|
-
if (VEHICLE_CLASSES.has(c)) return CLASS_RANK_VEHICLE;
|
|
25958
|
+
if (VEHICLE_CLASSES$1.has(c)) return CLASS_RANK_VEHICLE;
|
|
25957
25959
|
if (ANIMAL_CLASSES.has(c)) return CLASS_RANK_ANIMAL;
|
|
25958
25960
|
return CLASS_RANK_DEFAULT;
|
|
25959
25961
|
}
|
|
@@ -26556,7 +26558,7 @@ var FaceGalleryProvider = class {
|
|
|
26556
26558
|
const identities = await this.identityStore.listIdentities();
|
|
26557
26559
|
const nameMap = new Map(identities.map((i) => [i.id, i.name]));
|
|
26558
26560
|
const identityName = face.recognizedIdentityId ? nameMap.get(face.recognizedIdentityId) : void 0;
|
|
26559
|
-
const
|
|
26561
|
+
const cropUrl = this.cropUrlFor(face);
|
|
26560
26562
|
return {
|
|
26561
26563
|
faceId: face.id,
|
|
26562
26564
|
deviceId: face.deviceId,
|
|
@@ -26565,7 +26567,7 @@ var FaceGalleryProvider = class {
|
|
|
26565
26567
|
...face.recognizedIdentityId != null ? { recognizedIdentityId: face.recognizedIdentityId } : {},
|
|
26566
26568
|
...identityName !== void 0 ? { identityName } : {},
|
|
26567
26569
|
assigned: face.assigned,
|
|
26568
|
-
...
|
|
26570
|
+
...cropUrl !== void 0 ? { cropUrl } : {},
|
|
26569
26571
|
...face.faceBbox !== void 0 ? { faceBbox: face.faceBbox } : {},
|
|
26570
26572
|
...face.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: face.keyFrameMediaKey } : {},
|
|
26571
26573
|
...face.bestMatchScore != null ? { bestMatchScore: face.bestMatchScore } : {},
|
|
@@ -29685,9 +29687,15 @@ var TrackCloser = class {
|
|
|
29685
29687
|
await this.suppressZeroMedia(t.deviceId, t.trackId);
|
|
29686
29688
|
return;
|
|
29687
29689
|
}
|
|
29688
|
-
|
|
29690
|
+
let thumbnailLanded = ownedMedia.some((m) => m.kind === "thumbnail");
|
|
29691
|
+
if (outcome === "raster-fallback" && closure?.rasterFallback) {
|
|
29692
|
+
await this.commitRasterFallback(t, closure.rasterFallback.jpeg, closure.rasterFallback.timestamp);
|
|
29693
|
+
thumbnailLanded = true;
|
|
29694
|
+
}
|
|
29689
29695
|
await this.maybePromoteLastFrame(t, ownedMedia);
|
|
29690
|
-
|
|
29696
|
+
const hasRasterFallback = closure?.rasterFallback !== void 0;
|
|
29697
|
+
if (!thumbnailLanded) thumbnailLanded = await this.maybeDeriveThumbnailFromKeyFrame(t, ownedMedia, hasRasterFallback);
|
|
29698
|
+
if (!thumbnailLanded && closure?.rasterFallback) await this.commitRasterFallback(t, closure.rasterFallback.jpeg, closure.rasterFallback.timestamp);
|
|
29691
29699
|
this.deps.logger.info("track ended", {
|
|
29692
29700
|
tags: { deviceId: t.deviceId },
|
|
29693
29701
|
meta: {
|
|
@@ -29805,14 +29813,23 @@ var TrackCloser = class {
|
|
|
29805
29813
|
* and the rate could only ever be counted from the database, after the fact.
|
|
29806
29814
|
* A branch that drops work silently reads as "never happened".
|
|
29807
29815
|
*/
|
|
29808
|
-
async maybeDeriveThumbnailFromKeyFrame(t, ownedMedia) {
|
|
29809
|
-
if (ownedMedia.some((m) => m.kind === "thumbnail")) return;
|
|
29816
|
+
async maybeDeriveThumbnailFromKeyFrame(t, ownedMedia, hasRasterFallback) {
|
|
29817
|
+
if (ownedMedia.some((m) => m.kind === "thumbnail")) return true;
|
|
29810
29818
|
const derive = this.deps.deriveThumbnailFromKeyFrame;
|
|
29811
|
-
if (!derive)
|
|
29819
|
+
if (!derive) {
|
|
29820
|
+
if (!hasRasterFallback) this.logNoThumbnailAtClose(t, "derive-not-wired");
|
|
29821
|
+
return false;
|
|
29822
|
+
}
|
|
29812
29823
|
const keyFrame = ownedMedia.find((m) => m.kind === "keyFrame");
|
|
29813
|
-
if (!keyFrame)
|
|
29824
|
+
if (!keyFrame) {
|
|
29825
|
+
if (!hasRasterFallback) this.logNoThumbnailAtClose(t, "no-key-frame");
|
|
29826
|
+
return false;
|
|
29827
|
+
}
|
|
29814
29828
|
const bestSeen = this.deps.residents.bestSeenBbox(t.trackId);
|
|
29815
|
-
if (!bestSeen)
|
|
29829
|
+
if (!bestSeen) {
|
|
29830
|
+
if (!hasRasterFallback) this.logNoThumbnailAtClose(t, "no-best-seen-bbox");
|
|
29831
|
+
return false;
|
|
29832
|
+
}
|
|
29816
29833
|
try {
|
|
29817
29834
|
const derived = await derive({
|
|
29818
29835
|
mediaKey: keyFrame.key,
|
|
@@ -29824,17 +29841,23 @@ var TrackCloser = class {
|
|
|
29824
29841
|
},
|
|
29825
29842
|
frameWidth: bestSeen.frameWidth,
|
|
29826
29843
|
frameHeight: bestSeen.frameHeight,
|
|
29827
|
-
timestamp:
|
|
29844
|
+
timestamp: keyFrame.timestamp
|
|
29828
29845
|
});
|
|
29829
|
-
if (!derived)
|
|
29846
|
+
if (!derived) {
|
|
29847
|
+
if (!hasRasterFallback) this.logNoThumbnailAtClose(t, "derive-returned-null", { keyFrameKey: keyFrame.key });
|
|
29848
|
+
return false;
|
|
29849
|
+
}
|
|
29830
29850
|
const mediaStore = this.deps.mediaStore();
|
|
29831
|
-
if (!mediaStore)
|
|
29851
|
+
if (!mediaStore) {
|
|
29852
|
+
if (!hasRasterFallback) this.logNoThumbnailAtClose(t, "no-media-store");
|
|
29853
|
+
return false;
|
|
29854
|
+
}
|
|
29832
29855
|
await mediaStore.put({
|
|
29833
29856
|
deviceId: t.deviceId,
|
|
29834
29857
|
ownerKind: "track",
|
|
29835
29858
|
ownerId: t.trackId,
|
|
29836
29859
|
kind: "thumbnail",
|
|
29837
|
-
timestamp:
|
|
29860
|
+
timestamp: keyFrame.timestamp,
|
|
29838
29861
|
data: derived.thumbnail
|
|
29839
29862
|
});
|
|
29840
29863
|
await mediaStore.put({
|
|
@@ -29842,7 +29865,7 @@ var TrackCloser = class {
|
|
|
29842
29865
|
ownerKind: "track",
|
|
29843
29866
|
ownerId: t.trackId,
|
|
29844
29867
|
kind: "thumbnailSmall",
|
|
29845
|
-
timestamp:
|
|
29868
|
+
timestamp: keyFrame.timestamp,
|
|
29846
29869
|
data: derived.thumbnailSmall
|
|
29847
29870
|
});
|
|
29848
29871
|
this.deps.logger.info("thumbnail derived from keyFrame at close (no live best-shot landed)", {
|
|
@@ -29853,6 +29876,7 @@ var TrackCloser = class {
|
|
|
29853
29876
|
skewMs: derived.skewMs
|
|
29854
29877
|
}
|
|
29855
29878
|
});
|
|
29879
|
+
return true;
|
|
29856
29880
|
} catch (err) {
|
|
29857
29881
|
this.deps.logger.debug("close-time keyFrame thumbnail derive failed", {
|
|
29858
29882
|
tags: { deviceId: t.deviceId },
|
|
@@ -29861,10 +29885,11 @@ var TrackCloser = class {
|
|
|
29861
29885
|
error: String(err)
|
|
29862
29886
|
}
|
|
29863
29887
|
});
|
|
29864
|
-
this.logNoThumbnailAtClose(t, "derive-threw", {
|
|
29888
|
+
if (!hasRasterFallback) this.logNoThumbnailAtClose(t, "derive-threw", {
|
|
29865
29889
|
keyFrameKey: keyFrame.key,
|
|
29866
29890
|
error: String(err)
|
|
29867
29891
|
});
|
|
29892
|
+
return false;
|
|
29868
29893
|
}
|
|
29869
29894
|
}
|
|
29870
29895
|
/**
|
|
@@ -30734,6 +30759,7 @@ var PlateGalleryProvider = class {
|
|
|
30734
30759
|
logger;
|
|
30735
30760
|
refreshGallery;
|
|
30736
30761
|
emitPlateGalleryChanged;
|
|
30762
|
+
eventMediaBaseUrl;
|
|
30737
30763
|
constructor(deps) {
|
|
30738
30764
|
this.plateStore = deps.plateStore;
|
|
30739
30765
|
this.vehicleStore = deps.vehicleStore;
|
|
@@ -30743,6 +30769,7 @@ var PlateGalleryProvider = class {
|
|
|
30743
30769
|
this.logger = deps.logger;
|
|
30744
30770
|
this.refreshGallery = deps.refreshGallery;
|
|
30745
30771
|
this.emitPlateGalleryChanged = deps.emitPlateGalleryChanged;
|
|
30772
|
+
this.eventMediaBaseUrl = deps.eventMediaBaseUrl;
|
|
30746
30773
|
}
|
|
30747
30774
|
/** Best-effort emit — never allowed to fail the mutation it follows. */
|
|
30748
30775
|
safeEmitPlateGalleryChanged(payload) {
|
|
@@ -30750,6 +30777,12 @@ var PlateGalleryProvider = class {
|
|
|
30750
30777
|
this.emitPlateGalleryChanged(payload);
|
|
30751
30778
|
} catch {}
|
|
30752
30779
|
}
|
|
30780
|
+
cropUrlFor(plate) {
|
|
30781
|
+
if (plate.mediaKey === void 0 || plate.mediaKey === "") return void 0;
|
|
30782
|
+
const base = this.eventMediaBaseUrl();
|
|
30783
|
+
if (base === null) return void 0;
|
|
30784
|
+
return `${base}/${encodeURIComponent(plate.mediaKey)}`;
|
|
30785
|
+
}
|
|
30753
30786
|
async plateCropBase64(plate) {
|
|
30754
30787
|
if (plate.mediaKey) {
|
|
30755
30788
|
const m = await this.mediaStore.getByKey(plate.mediaKey);
|
|
@@ -30758,8 +30791,9 @@ var PlateGalleryProvider = class {
|
|
|
30758
30791
|
const media = await this.mediaStore.listByOwner("plate", plate.id);
|
|
30759
30792
|
return media.length > 0 ? media[0].base64 : void 0;
|
|
30760
30793
|
}
|
|
30761
|
-
async toPlateInfo(plate, nameMap) {
|
|
30762
|
-
const base64 = await this.plateCropBase64(plate);
|
|
30794
|
+
async toPlateInfo(plate, nameMap, opts) {
|
|
30795
|
+
const base64 = opts?.includeCrops ?? true ? await this.plateCropBase64(plate) : void 0;
|
|
30796
|
+
const cropUrl = this.cropUrlFor(plate);
|
|
30763
30797
|
const vehicleName = plate.recognizedVehicleId != null ? nameMap?.get(plate.recognizedVehicleId) : void 0;
|
|
30764
30798
|
return {
|
|
30765
30799
|
plateId: plate.id,
|
|
@@ -30774,7 +30808,8 @@ var PlateGalleryProvider = class {
|
|
|
30774
30808
|
...vehicleName !== void 0 ? { vehicleName } : {},
|
|
30775
30809
|
...plate.plateBbox !== void 0 ? { plateBbox: plate.plateBbox } : {},
|
|
30776
30810
|
...plate.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: plate.keyFrameMediaKey } : {},
|
|
30777
|
-
...base64 !== void 0 ? { base64 } : {}
|
|
30811
|
+
...base64 !== void 0 ? { base64 } : {},
|
|
30812
|
+
...cropUrl !== void 0 ? { cropUrl } : {}
|
|
30778
30813
|
};
|
|
30779
30814
|
}
|
|
30780
30815
|
async vehicleNameMap() {
|
|
@@ -30793,7 +30828,7 @@ var PlateGalleryProvider = class {
|
|
|
30793
30828
|
const plate = await this.plateStore.get(`plate-${input.trackId}`);
|
|
30794
30829
|
if (!plate || plate.deviceId !== input.deviceId) return null;
|
|
30795
30830
|
const nameMap = await this.vehicleNameMap();
|
|
30796
|
-
return this.toPlateInfo(plate, nameMap);
|
|
30831
|
+
return this.toPlateInfo(plate, nameMap, { includeCrops: false });
|
|
30797
30832
|
}
|
|
30798
30833
|
async getPlateMedia(input) {
|
|
30799
30834
|
return (await this.mediaStore.listByOwner("plate", input.plateId)).map((m) => ({
|
|
@@ -35934,6 +35969,19 @@ function createEventMediaHandler(deps) {
|
|
|
35934
35969
|
};
|
|
35935
35970
|
}
|
|
35936
35971
|
//#endregion
|
|
35972
|
+
//#region src/pipeline-analytics/copy-face-crop-preview.ts
|
|
35973
|
+
var TRACK_PREVIEW_KINDS = new Set(["thumbnail", "thumbnailSmall"]);
|
|
35974
|
+
async function copyFaceCropIfTrackHasNoThumbnail(store, input) {
|
|
35975
|
+
if ((await store.listTrackMedia(input.trackId)).some((m) => TRACK_PREVIEW_KINDS.has(m.kind))) return false;
|
|
35976
|
+
await store.putTrackThumbnail({
|
|
35977
|
+
deviceId: input.deviceId,
|
|
35978
|
+
trackId: input.trackId,
|
|
35979
|
+
timestamp: input.timestamp,
|
|
35980
|
+
data: input.crop
|
|
35981
|
+
});
|
|
35982
|
+
return true;
|
|
35983
|
+
}
|
|
35984
|
+
//#endregion
|
|
35937
35985
|
//#region src/pipeline-analytics/event-thumbnail-resolver.ts
|
|
35938
35986
|
/** Pick an event-OWNED media blob: the tight `crop`, else the native-res boxed
|
|
35939
35987
|
* `fullFrameBoxed`, else any available file. Both preferred kinds are RETIRED
|
|
@@ -35942,29 +35990,47 @@ function createEventMediaHandler(deps) {
|
|
|
35942
35990
|
function pickEventOwnedMedia(files) {
|
|
35943
35991
|
return files.find((f) => f.kind === "crop") ?? files.find((f) => f.kind === "fullFrameBoxed") ?? files[0];
|
|
35944
35992
|
}
|
|
35945
|
-
/** Pick a track's fallback media
|
|
35946
|
-
*
|
|
35947
|
-
*
|
|
35948
|
-
* Undefined when the
|
|
35993
|
+
/** Pick a track's fallback media when no subject crop exists: `thumbnailSmall`
|
|
35994
|
+
* → `thumbnail` → native `keyFrameSmall`/`keyFrame`/`fullFrame` → rolling
|
|
35995
|
+
* `lastFrame` → `firstFrame` → newest `snapshot` → any blob. A full frame
|
|
35996
|
+
* beats a class icon. Undefined when the owner has no media. */
|
|
35949
35997
|
function pickTrackFallbackMedia(files) {
|
|
35950
|
-
return files.find((f) => f.kind === "thumbnailSmall") ?? files.find((f) => f.kind === "thumbnail") ?? files.find((f) => f.kind === "lastFrame") ?? files.find((f) => f.kind === "firstFrame") ?? [...files].reverse().find((f) => f.kind === "snapshot") ?? files[files.length - 1];
|
|
35998
|
+
return files.find((f) => f.kind === "thumbnailSmall") ?? files.find((f) => f.kind === "thumbnail") ?? files.find((f) => f.kind === "keyFrameSmall") ?? files.find((f) => f.kind === "keyFrame") ?? files.find((f) => f.kind === "fullFrame") ?? files.find((f) => f.kind === "lastFrame") ?? files.find((f) => f.kind === "firstFrame") ?? [...files].reverse().find((f) => f.kind === "snapshot") ?? files[files.length - 1];
|
|
35951
35999
|
}
|
|
35952
36000
|
/**
|
|
35953
|
-
* Crop-forced resolution (the `?kind=crop`
|
|
35954
|
-
*
|
|
35955
|
-
*
|
|
35956
|
-
*
|
|
35957
|
-
*
|
|
35958
|
-
* returning undefined. A boxed frame (with the target's box drawn) is a real
|
|
35959
|
-
* preview; an empty tile is not. Clean is always tried FIRST so the common case
|
|
35960
|
-
* still serves an unboxed tile. Undefined only when the owner has NO media.
|
|
36001
|
+
* Crop-forced resolution (the `?kind=crop` / `?kind=thumbnailSmall` reel path).
|
|
36002
|
+
*
|
|
36003
|
+
* Ladder: body crop (track/event thumbnail) → tight face/plate crop → any
|
|
36004
|
+
* remaining image (keyFrame / lastFrame / …). The class icon is only honest
|
|
36005
|
+
* when the owner has no stored bytes at all.
|
|
35961
36006
|
*
|
|
35962
36007
|
* `pickClean` is injected because the clean-kind picker (`pickCleanMedia`) lives
|
|
35963
36008
|
* in the addon `index.ts` over the concrete `MediaFileKind` set; this keeps the
|
|
35964
36009
|
* composition pure + unit-testable here.
|
|
35965
36010
|
*/
|
|
35966
|
-
function resolveCropForcedMedia(files, pickClean) {
|
|
35967
|
-
return pickClean(files) ?? pickTrackFallbackMedia(files);
|
|
36011
|
+
function resolveCropForcedMedia(files, pickClean, detailFiles = []) {
|
|
36012
|
+
return pickClean(files) ?? pickClean(detailFiles) ?? pickTrackFallbackMedia([...files, ...detailFiles]);
|
|
36013
|
+
}
|
|
36014
|
+
/**
|
|
36015
|
+
* Event + track media (the body/scene) and face/plate media (the detail
|
|
36016
|
+
* crops). Face rows live under `face-${trackId}`; a track can close with
|
|
36017
|
+
* `hasEmbeddedFace` and ZERO track files (0e343f86).
|
|
36018
|
+
*/
|
|
36019
|
+
async function collectCropForcedMedia(source, id) {
|
|
36020
|
+
const eventFiles = await source.listByOwner("event", id);
|
|
36021
|
+
const trackFiles = await source.listByOwner("track", id);
|
|
36022
|
+
const ownerTrackId = trackFiles.length === 0 ? await source.getTrackIdForEvent(id) : null;
|
|
36023
|
+
const owningTrackFiles = ownerTrackId === null ? [] : await source.listByOwner("track", ownerTrackId);
|
|
36024
|
+
const trackId = ownerTrackId ?? id;
|
|
36025
|
+
const [faceFiles, plateFiles] = await Promise.all([source.listByOwner("face", `face-${trackId}`), source.listByOwner("plate", `plate-${trackId}`)]);
|
|
36026
|
+
return {
|
|
36027
|
+
bodyFiles: [
|
|
36028
|
+
...eventFiles,
|
|
36029
|
+
...trackFiles,
|
|
36030
|
+
...owningTrackFiles
|
|
36031
|
+
],
|
|
36032
|
+
detailFiles: [...faceFiles, ...plateFiles]
|
|
36033
|
+
};
|
|
35968
36034
|
}
|
|
35969
36035
|
/**
|
|
35970
36036
|
* Resolve the default-path media for an event id: the event's own media when it
|
|
@@ -35978,6 +36044,14 @@ async function resolveDefaultEventMedia(deps) {
|
|
|
35978
36044
|
return pickTrackFallbackMedia(await deps.listTrackMedia(trackId)) ?? null;
|
|
35979
36045
|
}
|
|
35980
36046
|
//#endregion
|
|
36047
|
+
//#region src/pipeline-analytics/ensure-track-hierarchy.ts
|
|
36048
|
+
async function ensureTrackHierarchyMedia(deps, input) {
|
|
36049
|
+
const tasks = [];
|
|
36050
|
+
if (!deps.isThumbnailLanded(input.trackId)) tasks.push(deps.captureBodyThumbnail(input));
|
|
36051
|
+
if (!deps.isKeyFrameLanded(input.trackId)) tasks.push(deps.captureKeyFrame(input));
|
|
36052
|
+
await Promise.all(tasks);
|
|
36053
|
+
}
|
|
36054
|
+
//#endregion
|
|
35981
36055
|
//#region src/pipeline-analytics/face-settings.ts
|
|
35982
36056
|
/**
|
|
35983
36057
|
* Per-device face-recognition settings. Cascade: a per-device override on top
|
|
@@ -36496,6 +36570,21 @@ var FaceRecognizer = class {
|
|
|
36496
36570
|
...held,
|
|
36497
36571
|
crop
|
|
36498
36572
|
});
|
|
36573
|
+
if (crop !== void 0 && input.frameHandle !== void 0) this.deps.onLiveHierarchy?.({
|
|
36574
|
+
deviceId: input.deviceId,
|
|
36575
|
+
trackId: c.trackId,
|
|
36576
|
+
timestamp: input.timestamp,
|
|
36577
|
+
bodyBbox: {
|
|
36578
|
+
x: c.bbox.x,
|
|
36579
|
+
y: c.bbox.y,
|
|
36580
|
+
w: c.bbox.w,
|
|
36581
|
+
h: c.bbox.h
|
|
36582
|
+
},
|
|
36583
|
+
frameWidth: input.frameWidth,
|
|
36584
|
+
frameHeight: input.frameHeight,
|
|
36585
|
+
cropPadding: input.cropPadding,
|
|
36586
|
+
frameHandle: input.frameHandle
|
|
36587
|
+
});
|
|
36499
36588
|
this.deps.logger.debug("face: best-face held", {
|
|
36500
36589
|
tags: {
|
|
36501
36590
|
deviceId: input.deviceId,
|
|
@@ -36645,6 +36734,22 @@ var FaceRecognizer = class {
|
|
|
36645
36734
|
});
|
|
36646
36735
|
return;
|
|
36647
36736
|
}
|
|
36737
|
+
try {
|
|
36738
|
+
await this.deps.copyCropToTrackPreview?.({
|
|
36739
|
+
deviceId,
|
|
36740
|
+
trackId,
|
|
36741
|
+
crop: held.crop,
|
|
36742
|
+
timestamp: held.timestamp
|
|
36743
|
+
});
|
|
36744
|
+
} catch (err) {
|
|
36745
|
+
this.deps.logger.debug("face: track preview copy failed", {
|
|
36746
|
+
tags: { deviceId },
|
|
36747
|
+
meta: {
|
|
36748
|
+
trackId,
|
|
36749
|
+
error: String(err)
|
|
36750
|
+
}
|
|
36751
|
+
});
|
|
36752
|
+
}
|
|
36648
36753
|
const keyFrameMediaKey = this.deps.getKeyFrameMediaKey?.(trackId);
|
|
36649
36754
|
try {
|
|
36650
36755
|
await this.deps.faceStore.insert({
|
|
@@ -39935,8 +40040,9 @@ var OverlayDetailStateStore = class {
|
|
|
39935
40040
|
*
|
|
39936
40041
|
* See docs/superpowers/specs/2026-07-17-package-zones-design.md §3.3 +
|
|
39937
40042
|
* docs/superpowers/specs/2026-07-17-detection-config-exposure-design.md
|
|
39938
|
-
* (surface C).
|
|
39939
|
-
*
|
|
40043
|
+
* (surface C). Confirmation is N frames (default 3); dwell default is 0
|
|
40044
|
+
* (an extra bar). The 15 s dwell plus a 5 s round-gap missed a real
|
|
40045
|
+
* parcel on device 615 (2026-08-24 13:40).
|
|
39940
40046
|
*/
|
|
39941
40047
|
var PackageDropSettingsSchema = require_dist.object({
|
|
39942
40048
|
/**
|
|
@@ -39957,13 +40063,20 @@ var PackageDropSettingsSchema = require_dist.object({
|
|
|
39957
40063
|
*/
|
|
39958
40064
|
packageDropEnabled: require_dist.boolean().default(true),
|
|
39959
40065
|
/**
|
|
39960
|
-
*
|
|
39961
|
-
*
|
|
39962
|
-
*
|
|
39963
|
-
*
|
|
39964
|
-
*
|
|
40066
|
+
* Frames the box must be re-seen on before it is a parked package.
|
|
40067
|
+
* Same bar as tracker `minHits`: one frame is a flicker, two can still
|
|
40068
|
+
* be a passer-by's bag, three is "it stayed". A settle burst of three
|
|
40069
|
+
* frames in ~1 s is enough; no second governor round after the courier
|
|
40070
|
+
* left. Device 615 2026-08-24 13:40 expired at 1 round / 1.5 s dwell.
|
|
40071
|
+
*/
|
|
40072
|
+
packageDropMinFrames: require_dist.number().int().min(1).max(20).default(3),
|
|
40073
|
+
/**
|
|
40074
|
+
* Extra OBSERVED dwell (seconds, since first-seen) on top of N frames.
|
|
40075
|
+
* 0 (the default) means the frame count is enough — a courier who drops
|
|
40076
|
+
* and leaves in 2 s still delivers. Raise this to reject a bag briefly
|
|
40077
|
+
* set down and snatched back.
|
|
39965
40078
|
*/
|
|
39966
|
-
packageDropDwellSec: require_dist.number().int().min(0).max(3600).default(
|
|
40079
|
+
packageDropDwellSec: require_dist.number().int().min(0).max(3600).default(0),
|
|
39967
40080
|
/** Reject tiny far-field blobs: min bbox area as a fraction of the frame. */
|
|
39968
40081
|
packageDropMinBboxAreaFrac: require_dist.number().min(0).max(1).default(.004),
|
|
39969
40082
|
/**
|
|
@@ -39981,13 +40094,15 @@ var PackageDropSettingsSchema = require_dist.object({
|
|
|
39981
40094
|
packageDropPickupEnabled: require_dist.boolean().default(true),
|
|
39982
40095
|
/**
|
|
39983
40096
|
* 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
|
-
*
|
|
40097
|
+
* `delivered` if a non-package track (person/vehicle/animal) was active IN
|
|
40098
|
+
* A CONFIGURED PACKAGE DROP-OFF ZONE within this many seconds BEFORE the
|
|
40099
|
+
* box first appeared — and a departure only counts as `picked-up` with
|
|
40100
|
+
* somebody in that zone around the disappearance. Whole-camera motion (a
|
|
40101
|
+
* walker at the far gate) does not count. 0 disables both gates. Device
|
|
40102
|
+
* 615's streetlight wedge cycled delivered→picked-up ~23×/night with
|
|
40103
|
+
* nobody in the drop-off zone. Default 300 s: wide enough to absorb the
|
|
40104
|
+
* motion-settle governor's cadence (the box is often first SEEN minutes
|
|
40105
|
+
* after the courier left).
|
|
39991
40106
|
*/
|
|
39992
40107
|
packageDropActivityWindowSec: require_dist.number().int().min(0).max(3600).default(300),
|
|
39993
40108
|
/**
|
|
@@ -40011,6 +40126,7 @@ function resolvePackageDropSettings(raw) {
|
|
|
40011
40126
|
};
|
|
40012
40127
|
return {
|
|
40013
40128
|
packageDropEnabled: pick("packageDropEnabled"),
|
|
40129
|
+
packageDropMinFrames: pick("packageDropMinFrames"),
|
|
40014
40130
|
packageDropDwellSec: pick("packageDropDwellSec"),
|
|
40015
40131
|
packageDropMinBboxAreaFrac: pick("packageDropMinBboxAreaFrac"),
|
|
40016
40132
|
packageDropMaxBboxAreaFrac: pick("packageDropMaxBboxAreaFrac"),
|
|
@@ -40268,29 +40384,6 @@ var DeferredBirthRegistry = class {
|
|
|
40268
40384
|
this.byDevice.delete(deviceKey);
|
|
40269
40385
|
}
|
|
40270
40386
|
};
|
|
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
40387
|
var ListGroupsInputSchema = require_dist.object({
|
|
40295
40388
|
deviceId: require_dist.number().int().optional(),
|
|
40296
40389
|
sinceMs: require_dist.number().int().optional(),
|
|
@@ -40300,66 +40393,11 @@ var ListGroupsInputSchema = require_dist.object({
|
|
|
40300
40393
|
});
|
|
40301
40394
|
var groupActions = require_dist.defineCustomActions({
|
|
40302
40395
|
"analytics.listGroups": require_dist.customAction(ListGroupsInputSchema, require_dist.object({
|
|
40303
|
-
groups: require_dist.array(AnalyticsGroupRecordSchema).readonly(),
|
|
40396
|
+
groups: require_dist.array(require_dist.AnalyticsGroupRecordSchema).readonly(),
|
|
40304
40397
|
hasMore: require_dist.boolean()
|
|
40305
40398
|
})),
|
|
40306
|
-
"analytics.getGroup": require_dist.customAction(require_dist.object({ groupId: require_dist.string().min(1) }), require_dist.object({ group: AnalyticsGroupDetailSchema.nullable() }))
|
|
40399
|
+
"analytics.getGroup": require_dist.customAction(require_dist.object({ groupId: require_dist.string().min(1) }), require_dist.object({ group: require_dist.AnalyticsGroupDetailSchema.nullable() }))
|
|
40307
40400
|
});
|
|
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
40401
|
function trackToLite(track) {
|
|
40364
40402
|
return {
|
|
40365
40403
|
trackId: track.trackId,
|
|
@@ -40369,56 +40407,18 @@ function trackToLite(track) {
|
|
|
40369
40407
|
lastSeen: track.lastSeen
|
|
40370
40408
|
};
|
|
40371
40409
|
}
|
|
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
40410
|
/**
|
|
40418
40411
|
* Gallery and digest read the same ladder: groupCrop, then the native-frame
|
|
40419
|
-
* pointer, then member
|
|
40420
|
-
*
|
|
40412
|
+
* pointer, then a member's clean full frame. Plate / face / member
|
|
40413
|
+
* thumbnails never reach the picker — a plate read overwrites the vehicle
|
|
40414
|
+
* first-level crop under the same `thumbnail` kind. A multi with no crop
|
|
40415
|
+
* still has a picture if the sealer stored `nativeKey`.
|
|
40421
40416
|
*/
|
|
40417
|
+
var DIGEST_MEMBER_FALLBACK_KINDS = new Set([
|
|
40418
|
+
"keyFrame",
|
|
40419
|
+
"keyFrameSmall",
|
|
40420
|
+
"fullFrame"
|
|
40421
|
+
]);
|
|
40422
40422
|
function digestMediaRows(input) {
|
|
40423
40423
|
const owned = [];
|
|
40424
40424
|
if (input.mediaKey !== null && input.mediaKey.length > 0) owned.push({
|
|
@@ -40432,7 +40432,7 @@ function digestMediaRows(input) {
|
|
|
40432
40432
|
timestamp: input.timestamp
|
|
40433
40433
|
});
|
|
40434
40434
|
if (owned.length > 0) return owned;
|
|
40435
|
-
return input.memberRows;
|
|
40435
|
+
return input.memberRows.filter((row) => DIGEST_MEMBER_FALLBACK_KINDS.has(row.kind));
|
|
40436
40436
|
}
|
|
40437
40437
|
var MATCH_WINDOW_MS = 1e3;
|
|
40438
40438
|
var MOVE_EPS = .35;
|
|
@@ -40986,6 +40986,34 @@ function toInstant(t) {
|
|
|
40986
40986
|
confidence: t.importance
|
|
40987
40987
|
};
|
|
40988
40988
|
}
|
|
40989
|
+
function memberBoxesAt(members, at, frame) {
|
|
40990
|
+
const w = Math.max(frame.width, 1);
|
|
40991
|
+
const h = Math.max(frame.height, 1);
|
|
40992
|
+
const boxes = [];
|
|
40993
|
+
for (const member of members) {
|
|
40994
|
+
let best = member.positions[0];
|
|
40995
|
+
let bestDt = Number.POSITIVE_INFINITY;
|
|
40996
|
+
for (const p of member.positions) {
|
|
40997
|
+
const dt = Math.abs(p.timestamp - at);
|
|
40998
|
+
if (dt <= 1e3 && dt < bestDt) {
|
|
40999
|
+
best = p;
|
|
41000
|
+
bestDt = dt;
|
|
41001
|
+
}
|
|
41002
|
+
}
|
|
41003
|
+
if (best === void 0 || bestDt === Number.POSITIVE_INFINITY) continue;
|
|
41004
|
+
boxes.push({
|
|
41005
|
+
bbox: {
|
|
41006
|
+
x: best.bbox.x / w,
|
|
41007
|
+
y: best.bbox.y / h,
|
|
41008
|
+
w: best.bbox.w / w,
|
|
41009
|
+
h: best.bbox.h / h
|
|
41010
|
+
},
|
|
41011
|
+
label: member.className,
|
|
41012
|
+
observedAt: best.timestamp
|
|
41013
|
+
});
|
|
41014
|
+
}
|
|
41015
|
+
return boxes;
|
|
41016
|
+
}
|
|
40989
41017
|
function toSealer(track) {
|
|
40990
41018
|
return {
|
|
40991
41019
|
trackId: track.trackId,
|
|
@@ -41002,7 +41030,11 @@ var GroupSealer = class {
|
|
|
41002
41030
|
constructor(deps) {
|
|
41003
41031
|
this.deps = deps;
|
|
41004
41032
|
}
|
|
41033
|
+
get processEnabled() {
|
|
41034
|
+
return this.deps.enabled ?? false;
|
|
41035
|
+
}
|
|
41005
41036
|
async flushIdle(buffer, now = this.deps.now()) {
|
|
41037
|
+
if (!this.processEnabled) return 0;
|
|
41006
41038
|
let written = 0;
|
|
41007
41039
|
for (const group of buffer.idleGroups(now, GROUP_SESSION_IDLE_MS)) if (await this.persistLiveGroup(group, buffer)) {
|
|
41008
41040
|
buffer.drop([group.id]);
|
|
@@ -41015,6 +41047,7 @@ var GroupSealer = class {
|
|
|
41015
41047
|
* into the live buffer, persist whoever is already idle, keep the rest in RAM.
|
|
41016
41048
|
*/
|
|
41017
41049
|
async recoverCamera(deviceId, buffer) {
|
|
41050
|
+
if (!this.processEnabled) return 0;
|
|
41018
41051
|
if (buffer.isPrimed(deviceId)) return 0;
|
|
41019
41052
|
const now = this.deps.now();
|
|
41020
41053
|
try {
|
|
@@ -41043,6 +41076,7 @@ var GroupSealer = class {
|
|
|
41043
41076
|
* is what wedged hub-main.
|
|
41044
41077
|
*/
|
|
41045
41078
|
async sealWindow(deviceId, since, until) {
|
|
41079
|
+
if (!this.processEnabled) return 0;
|
|
41046
41080
|
try {
|
|
41047
41081
|
const closed = await this.deps.trackStore.queryHistorical({
|
|
41048
41082
|
deviceId,
|
|
@@ -41157,26 +41191,54 @@ var GroupSealer = class {
|
|
|
41157
41191
|
return;
|
|
41158
41192
|
}
|
|
41159
41193
|
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
|
-
|
|
41194
|
+
const raw = await (0, sharp.default)(source.jpeg).removeAlpha().toColorspace("srgb").raw().toBuffer({ resolveWithObject: true });
|
|
41195
|
+
if (raw.info.channels === 3) {
|
|
41196
|
+
const union = {
|
|
41197
|
+
x: instant.ux * raw.info.width,
|
|
41198
|
+
y: instant.uy * raw.info.height,
|
|
41199
|
+
w: instant.uw * raw.info.width,
|
|
41200
|
+
h: instant.uh * raw.info.height
|
|
41201
|
+
};
|
|
41202
|
+
const crop = await cropGroupFromRgb(raw.data, raw.info.width, raw.info.height, union);
|
|
41203
|
+
const mediaKey = await this.deps.mediaStore.put({
|
|
41204
|
+
deviceId,
|
|
41205
|
+
ownerKind: "group",
|
|
41206
|
+
ownerId: draft.id,
|
|
41207
|
+
kind: "thumbnail",
|
|
41208
|
+
timestamp,
|
|
41209
|
+
data: crop
|
|
41210
|
+
});
|
|
41211
|
+
await this.deps.groupStore.setMediaKey(draft.id, mediaKey);
|
|
41212
|
+
}
|
|
41213
|
+
} catch (err) {
|
|
41214
|
+
this.deps.logger.warn("group sealer: crop failed", {
|
|
41215
|
+
tags: { deviceId },
|
|
41216
|
+
meta: {
|
|
41217
|
+
groupId: draft.id,
|
|
41218
|
+
error: String(err)
|
|
41219
|
+
}
|
|
41220
|
+
});
|
|
41221
|
+
}
|
|
41222
|
+
try {
|
|
41223
|
+
const composed = await composeGroupFrame({
|
|
41224
|
+
jpeg: source.jpeg,
|
|
41225
|
+
boxes: memberBoxesAt(instantMembers, timestamp, frame),
|
|
41226
|
+
at: timestamp,
|
|
41227
|
+
logger: this.deps.logger,
|
|
41228
|
+
deviceId,
|
|
41229
|
+
groupKey: draft.id
|
|
41230
|
+
});
|
|
41231
|
+
if (composed === null) return;
|
|
41232
|
+
await this.deps.mediaStore.put({
|
|
41170
41233
|
deviceId,
|
|
41171
41234
|
ownerKind: "group",
|
|
41172
41235
|
ownerId: draft.id,
|
|
41173
|
-
kind: "
|
|
41236
|
+
kind: "fullFrameBoxed",
|
|
41174
41237
|
timestamp,
|
|
41175
|
-
data:
|
|
41238
|
+
data: composed.bytes
|
|
41176
41239
|
});
|
|
41177
|
-
await this.deps.groupStore.setMediaKey(draft.id, mediaKey);
|
|
41178
41240
|
} catch (err) {
|
|
41179
|
-
this.deps.logger.warn("group sealer:
|
|
41241
|
+
this.deps.logger.warn("group sealer: boxed frame failed", {
|
|
41180
41242
|
tags: { deviceId },
|
|
41181
41243
|
meta: {
|
|
41182
41244
|
groupId: draft.id,
|
|
@@ -41201,6 +41263,37 @@ var GroupSealer = class {
|
|
|
41201
41263
|
}
|
|
41202
41264
|
};
|
|
41203
41265
|
//#endregion
|
|
41266
|
+
//#region src/pipeline-analytics/group/group-cursor.ts
|
|
41267
|
+
function encodeGroupCursor(cursor) {
|
|
41268
|
+
return Buffer.from(JSON.stringify({
|
|
41269
|
+
c: cursor.closedAt,
|
|
41270
|
+
i: cursor.groupId
|
|
41271
|
+
}), "utf8").toString("base64url");
|
|
41272
|
+
}
|
|
41273
|
+
function decodeGroupCursor(raw) {
|
|
41274
|
+
let parsed;
|
|
41275
|
+
try {
|
|
41276
|
+
parsed = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
|
|
41277
|
+
} catch {
|
|
41278
|
+
throw new Error("listGroups: malformed cursor");
|
|
41279
|
+
}
|
|
41280
|
+
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");
|
|
41281
|
+
return {
|
|
41282
|
+
closedAt: parsed.c,
|
|
41283
|
+
groupId: parsed.i
|
|
41284
|
+
};
|
|
41285
|
+
}
|
|
41286
|
+
function compareGroupDesc(a, b) {
|
|
41287
|
+
if (a.closedAt !== b.closedAt) return b.closedAt - a.closedAt;
|
|
41288
|
+
if (a.id === b.id) return 0;
|
|
41289
|
+
return a.id < b.id ? 1 : -1;
|
|
41290
|
+
}
|
|
41291
|
+
/** True when `row` sits strictly AFTER the cursor in DESC order. */
|
|
41292
|
+
function isAfterGroupCursor(row, cursor) {
|
|
41293
|
+
if (row.closedAt < cursor.closedAt) return true;
|
|
41294
|
+
return row.closedAt === cursor.closedAt && row.id < cursor.groupId;
|
|
41295
|
+
}
|
|
41296
|
+
//#endregion
|
|
41204
41297
|
//#region src/pipeline-analytics/group/group-store.ts
|
|
41205
41298
|
/**
|
|
41206
41299
|
* The group ENTITY store — a co-moving partition of one session, with one
|
|
@@ -41462,6 +41555,24 @@ var TrackGroupStore = class {
|
|
|
41462
41555
|
data: r.data
|
|
41463
41556
|
}));
|
|
41464
41557
|
}
|
|
41558
|
+
/**
|
|
41559
|
+
* Membership for a page of groups in ONE query. `listGroupRecords` used to
|
|
41560
|
+
* call {@link listMembers} per row — 60 groups is 60 settings-store RPCs,
|
|
41561
|
+
* ~20 s, and the viewer feed times out into a retry loop.
|
|
41562
|
+
*/
|
|
41563
|
+
async listMembersForGroups(groupIds) {
|
|
41564
|
+
if (groupIds.length === 0) return [];
|
|
41565
|
+
return (await this.store.query.query({
|
|
41566
|
+
collection: TRACK_GROUP_MEMBERS_COLLECTION,
|
|
41567
|
+
filter: {
|
|
41568
|
+
whereIn: { groupId: [...groupIds] },
|
|
41569
|
+
limit: 1e4
|
|
41570
|
+
}
|
|
41571
|
+
})).map((r) => this.normalizeMember({
|
|
41572
|
+
id: r.id,
|
|
41573
|
+
data: r.data
|
|
41574
|
+
}));
|
|
41575
|
+
}
|
|
41465
41576
|
async membersOfTracks(trackIds) {
|
|
41466
41577
|
const out = [];
|
|
41467
41578
|
for (const trackId of trackIds) {
|
|
@@ -41477,20 +41588,29 @@ var TrackGroupStore = class {
|
|
|
41477
41588
|
return out;
|
|
41478
41589
|
}
|
|
41479
41590
|
/**
|
|
41480
|
-
* Newest-closed page for the
|
|
41591
|
+
* Newest-closed page for the Groups feed.
|
|
41481
41592
|
*
|
|
41482
41593
|
* Time filter is overlap (`closedAt >= since` and `openedAt <= until`),
|
|
41483
41594
|
* same as {@link listOverlapping}. Pagination is applied AFTER the window
|
|
41484
41595
|
* so a day cursor is honest rather than a raw-row offset that skips live
|
|
41485
|
-
* groups on a busy camera.
|
|
41596
|
+
* groups on a busy camera. `deviceIds: []` is an empty page (same as
|
|
41597
|
+
* `listRecentTracks`); omit the field for every camera. `deviceId` stays
|
|
41598
|
+
* as the single-camera alias the custom-action bridge still sends.
|
|
41486
41599
|
*/
|
|
41487
41600
|
async listRecent(input) {
|
|
41488
41601
|
const limit = Math.min(Math.max(input.limit ?? 40, 1), 100);
|
|
41489
41602
|
const offset = Math.max(input.offset ?? 0, 0);
|
|
41490
|
-
const
|
|
41603
|
+
const deviceIds = input.deviceIds !== void 0 ? input.deviceIds : input.deviceId !== void 0 ? [input.deviceId] : void 0;
|
|
41604
|
+
if (deviceIds !== void 0 && deviceIds.length === 0) return {
|
|
41605
|
+
groups: [],
|
|
41606
|
+
hasMore: false,
|
|
41607
|
+
nextCursor: null
|
|
41608
|
+
};
|
|
41609
|
+
const cursor = input.cursor !== void 0 && input.cursor.length > 0 ? decodeGroupCursor(input.cursor) : null;
|
|
41610
|
+
const filtered = (await this.store.query.query({
|
|
41491
41611
|
collection: TRACK_GROUPS_COLLECTION,
|
|
41492
41612
|
filter: {
|
|
41493
|
-
...
|
|
41613
|
+
...deviceIds !== void 0 && deviceIds.length === 1 ? { where: { deviceId: deviceIds[0] } } : deviceIds !== void 0 ? { whereIn: { deviceId: [...deviceIds] } } : {},
|
|
41494
41614
|
orderBy: {
|
|
41495
41615
|
field: "closedAt",
|
|
41496
41616
|
direction: "desc"
|
|
@@ -41503,11 +41623,20 @@ var TrackGroupStore = class {
|
|
|
41503
41623
|
})).filter((g) => {
|
|
41504
41624
|
if (input.sinceMs !== void 0 && g.closedAt < input.sinceMs) return false;
|
|
41505
41625
|
if (input.untilMs !== void 0 && g.openedAt > input.untilMs) return false;
|
|
41626
|
+
if (cursor !== null && !isAfterGroupCursor(g, cursor)) return false;
|
|
41506
41627
|
return true;
|
|
41507
|
-
}).
|
|
41628
|
+
}).toSorted(compareGroupDesc);
|
|
41629
|
+
const slice = (cursor !== null ? filtered : filtered.slice(offset)).slice(0, limit + 1);
|
|
41630
|
+
const groups = slice.slice(0, limit);
|
|
41631
|
+
const hasMore = slice.length > limit;
|
|
41632
|
+
const last = groups[groups.length - 1];
|
|
41508
41633
|
return {
|
|
41509
|
-
groups
|
|
41510
|
-
hasMore
|
|
41634
|
+
groups,
|
|
41635
|
+
hasMore,
|
|
41636
|
+
nextCursor: hasMore && last !== void 0 ? encodeGroupCursor({
|
|
41637
|
+
closedAt: last.closedAt,
|
|
41638
|
+
groupId: last.id
|
|
41639
|
+
}) : null
|
|
41511
41640
|
};
|
|
41512
41641
|
}
|
|
41513
41642
|
/** Groups whose life overlaps `[since, until]` on one camera. */
|
|
@@ -42839,7 +42968,7 @@ function evaluateStationaryPromotion(input) {
|
|
|
42839
42968
|
};
|
|
42840
42969
|
}
|
|
42841
42970
|
var DEFAULT_PACKAGE_STILLNESS_CONFIG = {
|
|
42842
|
-
|
|
42971
|
+
minHits: 3,
|
|
42843
42972
|
roundGapMs: 5e3,
|
|
42844
42973
|
ttlMs: 5 * 6e4,
|
|
42845
42974
|
assocIou: .3
|
|
@@ -42872,8 +43001,9 @@ var PackageStillnessTracker = class {
|
|
|
42872
43001
|
const prev = next[index];
|
|
42873
43002
|
if (prev === void 0) continue;
|
|
42874
43003
|
consumed.add(prev.id);
|
|
42875
|
-
const updated = confirm(prev, input, s
|
|
42876
|
-
|
|
43004
|
+
const updated = confirm(prev, input, s);
|
|
43005
|
+
const minHits = input.minHits ?? this.config.minHits;
|
|
43006
|
+
if (!(updated.promotedAt === null && updated.activityBackedAtOpen && updated.hits >= minHits && input.timestamp - updated.firstSeenAt >= input.minDwellMs)) {
|
|
42877
43007
|
next[index] = updated;
|
|
42878
43008
|
continue;
|
|
42879
43009
|
}
|
|
@@ -42897,7 +43027,7 @@ var PackageStillnessTracker = class {
|
|
|
42897
43027
|
id: c.id,
|
|
42898
43028
|
className: c.className,
|
|
42899
43029
|
bbox: { ...c.bbox },
|
|
42900
|
-
|
|
43030
|
+
hits: c.hits,
|
|
42901
43031
|
firstSeenAt: c.firstSeenAt,
|
|
42902
43032
|
lastSeenAt: c.lastSeenAt,
|
|
42903
43033
|
activityBackedAtOpen: c.activityBackedAtOpen
|
|
@@ -42955,7 +43085,7 @@ var PackageStillnessTracker = class {
|
|
|
42955
43085
|
frameHeight: input.frameHeight,
|
|
42956
43086
|
firstSeenAt: input.timestamp,
|
|
42957
43087
|
lastSeenAt: input.timestamp,
|
|
42958
|
-
|
|
43088
|
+
hits: 1,
|
|
42959
43089
|
promotedAt: null,
|
|
42960
43090
|
activityBackedAtOpen,
|
|
42961
43091
|
...s.trackId !== void 0 ? { sourceTrackId: s.trackId } : {},
|
|
@@ -42965,18 +43095,16 @@ var PackageStillnessTracker = class {
|
|
|
42965
43095
|
};
|
|
42966
43096
|
}
|
|
42967
43097
|
};
|
|
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;
|
|
43098
|
+
/** Re-confirm a candidate with this frame's box. Every sighting is a hit —
|
|
43099
|
+
* a settle burst of three frames in one second is three confirmations. */
|
|
43100
|
+
function confirm(prev, input, s) {
|
|
42973
43101
|
return {
|
|
42974
43102
|
...prev,
|
|
42975
43103
|
bbox: { ...s.bbox },
|
|
42976
43104
|
frameWidth: input.frameWidth,
|
|
42977
43105
|
frameHeight: input.frameHeight,
|
|
42978
43106
|
lastSeenAt: input.timestamp,
|
|
42979
|
-
|
|
43107
|
+
hits: prev.hits + 1,
|
|
42980
43108
|
...s.trackId !== void 0 ? { sourceTrackId: s.trackId } : {},
|
|
42981
43109
|
...s.label !== void 0 ? { label: s.label } : {},
|
|
42982
43110
|
...s.confidence !== void 0 ? { confidence: s.confidence } : {},
|
|
@@ -43001,6 +43129,31 @@ function toEntry(c) {
|
|
|
43001
43129
|
};
|
|
43002
43130
|
}
|
|
43003
43131
|
//#endregion
|
|
43132
|
+
//#region src/pipeline-analytics/pipeline/package/package-zone-activity.ts
|
|
43133
|
+
/**
|
|
43134
|
+
* Whether a live track is motion inside a configured package drop-off zone.
|
|
43135
|
+
*
|
|
43136
|
+
* The stillness activity gate used to stamp ANY non-package track on the
|
|
43137
|
+
* camera. A walker at the far gate then "delivered" the streetlight wedge
|
|
43138
|
+
* on the doormat. The operator's prerequisite is motion in the drop-off
|
|
43139
|
+
* zone the package rule points at — the same zones `PackageDropDetector`
|
|
43140
|
+
* uses for the centroid gate.
|
|
43141
|
+
*/
|
|
43142
|
+
function isDelivererInPackageZone(trackZones, packageZoneIds) {
|
|
43143
|
+
if (packageZoneIds.size === 0 || trackZones.length === 0) return false;
|
|
43144
|
+
for (const z of trackZones) if (packageZoneIds.has(z)) return true;
|
|
43145
|
+
return false;
|
|
43146
|
+
}
|
|
43147
|
+
/** Zone ids referenced by enabled `package`-stage rules. Disabled rules do not count. */
|
|
43148
|
+
function collectPackageZoneIds(rules) {
|
|
43149
|
+
const ids = /* @__PURE__ */ new Set();
|
|
43150
|
+
for (const r of rules) {
|
|
43151
|
+
if (r.enabled === false) continue;
|
|
43152
|
+
for (const z of r.zoneIds) ids.add(z);
|
|
43153
|
+
}
|
|
43154
|
+
return ids;
|
|
43155
|
+
}
|
|
43156
|
+
//#endregion
|
|
43004
43157
|
//#region src/pipeline-analytics/pipeline/stationary/stationary-zones.ts
|
|
43005
43158
|
/**
|
|
43006
43159
|
* Zone ids whose 0–1 polygon contains the entry's normalised bbox centroid.
|
|
@@ -44177,6 +44330,12 @@ var PlateRecognizer = class {
|
|
|
44177
44330
|
text: c.plateText,
|
|
44178
44331
|
score: c.plateScore,
|
|
44179
44332
|
bbox: c.plateBbox,
|
|
44333
|
+
bodyBbox: {
|
|
44334
|
+
x: c.bbox.x,
|
|
44335
|
+
y: c.bbox.y,
|
|
44336
|
+
w: c.bbox.w,
|
|
44337
|
+
h: c.bbox.h
|
|
44338
|
+
},
|
|
44180
44339
|
timestamp: input.timestamp,
|
|
44181
44340
|
frameWidth: input.frameWidth,
|
|
44182
44341
|
frameHeight: input.frameHeight,
|
|
@@ -44250,6 +44409,16 @@ var PlateRecognizer = class {
|
|
|
44250
44409
|
}
|
|
44251
44410
|
});
|
|
44252
44411
|
}
|
|
44412
|
+
if (crop !== void 0 && input.frameHandle !== void 0 && input.bodyBbox !== void 0) this.deps.onLiveHierarchy?.({
|
|
44413
|
+
deviceId: input.deviceId,
|
|
44414
|
+
trackId: input.trackId,
|
|
44415
|
+
timestamp: input.timestamp,
|
|
44416
|
+
bodyBbox: input.bodyBbox,
|
|
44417
|
+
frameWidth: input.frameWidth,
|
|
44418
|
+
frameHeight: input.frameHeight,
|
|
44419
|
+
cropPadding: input.cropPadding,
|
|
44420
|
+
frameHandle: input.frameHandle
|
|
44421
|
+
});
|
|
44253
44422
|
this.bestPlate.set(input.trackId, {
|
|
44254
44423
|
text: input.text,
|
|
44255
44424
|
score: input.score,
|
|
@@ -48284,14 +48453,23 @@ function isAfterCursor(row, cursor) {
|
|
|
48284
48453
|
if (row.lastSeen < cursor.lastSeen) return true;
|
|
48285
48454
|
return row.lastSeen === cursor.lastSeen && row.id < cursor.trackId;
|
|
48286
48455
|
}
|
|
48456
|
+
var VEHICLE_CLASSES = new Set([
|
|
48457
|
+
"vehicle",
|
|
48458
|
+
"car",
|
|
48459
|
+
"truck",
|
|
48460
|
+
"bus"
|
|
48461
|
+
]);
|
|
48462
|
+
function motionlessMaxPxForClass(className) {
|
|
48463
|
+
return VEHICLE_CLASSES.has(className.toLowerCase()) ? 8 : 40;
|
|
48464
|
+
}
|
|
48287
48465
|
function netDisplacementPx(positions) {
|
|
48288
48466
|
const first = positions[0];
|
|
48289
48467
|
const last = positions[positions.length - 1];
|
|
48290
48468
|
if (!first || !last) return 0;
|
|
48291
48469
|
return Math.hypot(last.x - first.x, last.y - first.y);
|
|
48292
48470
|
}
|
|
48293
|
-
function isShortMotionlessTrack(lifeMs, movedPx) {
|
|
48294
|
-
return lifeMs <= 25e3 && movedPx <=
|
|
48471
|
+
function isShortMotionlessTrack(lifeMs, movedPx, className) {
|
|
48472
|
+
return lifeMs <= 25e3 && movedPx <= motionlessMaxPxForClass(className);
|
|
48295
48473
|
}
|
|
48296
48474
|
//#endregion
|
|
48297
48475
|
//#region src/pipeline-analytics/store/track-store.ts
|
|
@@ -48875,7 +49053,7 @@ var TrackStore = class {
|
|
|
48875
49053
|
for (const [trackId, t] of this.active) {
|
|
48876
49054
|
if (nowMs - t.lastSeen < this.config.ttlMs) continue;
|
|
48877
49055
|
t.active = false;
|
|
48878
|
-
if (t.positions.length > 0 && isShortMotionlessTrack(t.lastSeen - t.firstSeen, netDisplacementPx(t.positions))) t.closedReason = "stationary-promotion";
|
|
49056
|
+
if (t.positions.length > 0 && isShortMotionlessTrack(t.lastSeen - t.firstSeen, netDisplacementPx(t.positions), t.className)) t.closedReason = "stationary-promotion";
|
|
48879
49057
|
const record = cloneTrack(t);
|
|
48880
49058
|
try {
|
|
48881
49059
|
await this.persistCompleted(record);
|
|
@@ -49904,6 +50082,28 @@ var TrackStore = class {
|
|
|
49904
50082
|
return this.rowToTrack(row.id, row.data);
|
|
49905
50083
|
}
|
|
49906
50084
|
/**
|
|
50085
|
+
* Batch read of persisted tracks. `listGroupRecords` cannot call
|
|
50086
|
+
* {@link getPersistedByTrackId} per member — that is one settings-store RPC
|
|
50087
|
+
* per row and a 60-group page takes ~20 s, which the viewer feed times out.
|
|
50088
|
+
*/
|
|
50089
|
+
async getPersistedByTrackIds(trackIds) {
|
|
50090
|
+
if (trackIds.length === 0) return [];
|
|
50091
|
+
const PAGE = 500;
|
|
50092
|
+
const out = [];
|
|
50093
|
+
for (let i = 0; i < trackIds.length; i += PAGE) {
|
|
50094
|
+
const chunk = trackIds.slice(i, i + PAGE);
|
|
50095
|
+
const rows = await this.store.query.query({
|
|
50096
|
+
collection: TRACKS_COLLECTION,
|
|
50097
|
+
filter: {
|
|
50098
|
+
whereIn: { id: [...chunk] },
|
|
50099
|
+
limit: chunk.length
|
|
50100
|
+
}
|
|
50101
|
+
});
|
|
50102
|
+
for (const row of rows) out.push(this.rowToTrack(row.id, row.data));
|
|
50103
|
+
}
|
|
50104
|
+
return out;
|
|
50105
|
+
}
|
|
50106
|
+
/**
|
|
49907
50107
|
* Persist a SYNTHETIC track (a sensor/control event projected into the
|
|
49908
50108
|
* unified track store). Unlike `persistCompleted` there is no live in-RAM
|
|
49909
50109
|
* state and no trajectory envelope (synthetic tracks carry no positions) —
|
|
@@ -54976,11 +55176,22 @@ function buildGlobalSettingsSchema(cap) {
|
|
|
54976
55176
|
description: "Also emit a package-picked-up event when the delivered package leaves.",
|
|
54977
55177
|
default: PACKAGE_DROP_DEFAULTS.packageDropPickupEnabled
|
|
54978
55178
|
},
|
|
55179
|
+
{
|
|
55180
|
+
type: "slider",
|
|
55181
|
+
key: "packageDropMinFrames",
|
|
55182
|
+
label: "Confirmation frames",
|
|
55183
|
+
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.",
|
|
55184
|
+
min: 1,
|
|
55185
|
+
max: 10,
|
|
55186
|
+
step: 1,
|
|
55187
|
+
default: PACKAGE_DROP_DEFAULTS.packageDropMinFrames,
|
|
55188
|
+
showValue: true
|
|
55189
|
+
},
|
|
54979
55190
|
{
|
|
54980
55191
|
type: "slider",
|
|
54981
55192
|
key: "packageDropDwellSec",
|
|
54982
55193
|
label: "Minimum dwell",
|
|
54983
|
-
description: "
|
|
55194
|
+
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
55195
|
min: 0,
|
|
54985
55196
|
max: 300,
|
|
54986
55197
|
step: 5,
|
|
@@ -55014,7 +55225,7 @@ function buildGlobalSettingsSchema(cap) {
|
|
|
55014
55225
|
type: "slider",
|
|
55015
55226
|
key: "packageDropActivityWindowSec",
|
|
55016
55227
|
label: "Deliverer window",
|
|
55017
|
-
description: "A delivery only counts if a person or vehicle was
|
|
55228
|
+
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
55229
|
min: 0,
|
|
55019
55230
|
max: 1800,
|
|
55020
55231
|
step: 30,
|
|
@@ -56848,10 +57059,12 @@ var CONFIRMATION_CROP_MAX_WIDTH = 320;
|
|
|
56848
57059
|
* a late confirmation is still worth running; see `DEFAULT_CAPTURE_PRIORITY`). */
|
|
56849
57060
|
var ENRICHMENT_CAPTURE_STALE_MS = 3e3;
|
|
56850
57061
|
/** Reconcile cadence for the per-device `zones` / `zoneRules` slice handles.
|
|
56851
|
-
* The `device.state-changed` push
|
|
56852
|
-
*
|
|
56853
|
-
*
|
|
56854
|
-
|
|
57062
|
+
* The `device.state-changed` push reaches a forked child through
|
|
57063
|
+
* `createEventBusSliceSource` (the DeviceProxy no longer waits on
|
|
57064
|
+
* `live.onEvent`). Events are still lossy telemetry (D8); this timer
|
|
57065
|
+
* is the RPC reconcile, not the mechanism. Five minutes matches the
|
|
57066
|
+
* viewer's `RECONCILE_MS`. */
|
|
57067
|
+
var ZONE_SLICE_RECONCILE_MS = 3e5;
|
|
56855
57068
|
/** How often the evaluating node re-reads the central scene set. */
|
|
56856
57069
|
var SCENE_RELOAD_INTERVAL_MS = 3e4;
|
|
56857
57070
|
/** How long the install coordinates are trusted between settings reads. */
|
|
@@ -56915,38 +57128,31 @@ var MOTION_EVENT_HEARTBEAT_MS = 5e3;
|
|
|
56915
57128
|
*
|
|
56916
57129
|
* `thumbnail` joined the clean set on 2026-07-17: the track best-thumbnail is
|
|
56917
57130
|
* now a subject-centered crop (event-media-dispatcher), and for DEGENERATE
|
|
56918
|
-
* tracks (no object event → no `crop`) it is the ONLY subject-centered media
|
|
56919
|
-
*
|
|
56920
|
-
*
|
|
56921
|
-
*
|
|
56922
|
-
*
|
|
56923
|
-
*
|
|
56924
|
-
*
|
|
56925
|
-
*
|
|
56926
|
-
*
|
|
56927
|
-
* over 404). The retired-but-still-stored kinds (`crop`) stay listed so old rows
|
|
56928
|
-
* keep resolving.
|
|
57131
|
+
* tracks (no object event → no `crop`) it is the ONLY subject-centered media.
|
|
57132
|
+
*
|
|
57133
|
+
* Full-scene kinds (`fullFrame`, `keyFrame`, `keyFrameSmall`) stay out of
|
|
57134
|
+
* this list so a crop is preferred when one exists. `resolveCropForcedMedia`
|
|
57135
|
+
* then falls through to those full-scene kinds — a keyFrame beats an icon.
|
|
57136
|
+
* An *explicit* `?kind=keyFrame` / `?kind=snapshot` still matches exactly, so
|
|
57137
|
+
* videoclips and other kind-specific fetches keep working.
|
|
57138
|
+
*
|
|
57139
|
+
* The retired-but-still-stored kind (`crop`) stays listed so old rows resolve.
|
|
56929
57140
|
*/
|
|
56930
57141
|
var CLEAN_MEDIA_KINDS = [
|
|
56931
57142
|
"crop",
|
|
56932
57143
|
"thumbnail",
|
|
56933
|
-
"thumbnailSmall"
|
|
56934
|
-
"fullFrame",
|
|
56935
|
-
"keyFrame",
|
|
56936
|
-
"keyFrameSmall"
|
|
57144
|
+
"thumbnailSmall"
|
|
56937
57145
|
];
|
|
56938
57146
|
/**
|
|
56939
|
-
* Pick
|
|
56940
|
-
* `
|
|
56941
|
-
* {@link CLEAN_MEDIA_KINDS}
|
|
56942
|
-
*
|
|
57147
|
+
* Pick media for a crop-forced request: the exact `preferKind` when present
|
|
57148
|
+
* (so `?kind=snapshot` still serves a snapshot), else the first available
|
|
57149
|
+
* subject crop in {@link CLEAN_MEDIA_KINDS}. Returns undefined when no crop
|
|
57150
|
+
* is in the list — the caller then falls through to any remaining image
|
|
57151
|
+
* (`resolveCropForcedMedia`); the class icon is only for a total miss.
|
|
56943
57152
|
*/
|
|
56944
57153
|
function pickCleanMedia(files, preferKind) {
|
|
56945
|
-
const
|
|
56946
|
-
if (
|
|
56947
|
-
const exact = files.find((f) => f.kind === preferKind);
|
|
56948
|
-
if (exact) return exact;
|
|
56949
|
-
}
|
|
57154
|
+
const exact = files.find((f) => f.kind === preferKind);
|
|
57155
|
+
if (exact) return exact;
|
|
56950
57156
|
for (const kind of CLEAN_MEDIA_KINDS) {
|
|
56951
57157
|
const found = files.find((f) => f.kind === kind);
|
|
56952
57158
|
if (found) return found;
|
|
@@ -57439,11 +57645,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
57439
57645
|
*/
|
|
57440
57646
|
packageStillness = new PackageStillnessTracker({ newId: () => (0, node_crypto.randomUUID)() });
|
|
57441
57647
|
/**
|
|
57442
|
-
* Frame-clock timestamp of the last NON-package track seen
|
|
57443
|
-
*
|
|
57444
|
-
* deliverer").
|
|
57445
|
-
*
|
|
57446
|
-
*
|
|
57648
|
+
* Frame-clock timestamp of the last NON-package track seen IN A PACKAGE
|
|
57649
|
+
* DROP-OFF ZONE per device — the activity mirror behind the package
|
|
57650
|
+
* activity gate ("a parcel needs a deliverer"). A walker at the far gate
|
|
57651
|
+
* does not stamp this. Deliberately not {@link lastTrackActivityMs}: that
|
|
57652
|
+
* map is stamped by ANY live track, including the package-class FP itself,
|
|
57653
|
+
* which would let the streetlight wedge vouch for its own delivery.
|
|
57447
57654
|
*/
|
|
57448
57655
|
lastNonPackageActivityAt = /* @__PURE__ */ new Map();
|
|
57449
57656
|
/** Last time the oversize-sighting drop was logged per device — one line
|
|
@@ -58409,6 +58616,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
58409
58616
|
}, trackId);
|
|
58410
58617
|
},
|
|
58411
58618
|
markEmbeddedFace: (trackId) => stores.trackStore.markEmbeddedFace(trackId),
|
|
58619
|
+
copyCropToTrackPreview: async (input) => {
|
|
58620
|
+
await copyFaceCropIfTrackHasNoThumbnail({
|
|
58621
|
+
listTrackMedia: (trackId) => stores.mediaStore.listByOwner("track", trackId),
|
|
58622
|
+
putTrackThumbnail: (put) => stores.mediaStore.put({
|
|
58623
|
+
deviceId: put.deviceId,
|
|
58624
|
+
ownerKind: "track",
|
|
58625
|
+
ownerId: put.trackId,
|
|
58626
|
+
kind: "thumbnail",
|
|
58627
|
+
timestamp: put.timestamp,
|
|
58628
|
+
data: put.data
|
|
58629
|
+
})
|
|
58630
|
+
}, input);
|
|
58631
|
+
},
|
|
58412
58632
|
getKeyFrameMediaKey: (trackId) => this.residents.keyFrameKey(trackId),
|
|
58413
58633
|
emitFaceGalleryChanged: (payload) => this.emitFaceGalleryChanged(payload),
|
|
58414
58634
|
onSemanticMatch: (match) => this.promoteSemanticRecognition({
|
|
@@ -58423,6 +58643,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
58423
58643
|
confidence: match.confidence,
|
|
58424
58644
|
...match.frameHandle !== void 0 ? { frameHandle: match.frameHandle } : {}
|
|
58425
58645
|
}),
|
|
58646
|
+
onLiveHierarchy: (input) => this.requestTrackHierarchy(input),
|
|
58426
58647
|
logger: logger.child("FaceRecognizer")
|
|
58427
58648
|
});
|
|
58428
58649
|
this.faceRecognizer.refreshGallery();
|
|
@@ -58455,6 +58676,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
58455
58676
|
deriveCropFromKeyFrame,
|
|
58456
58677
|
getKeyFrameMediaKey: (trackId) => this.residents.keyFrameKey(trackId),
|
|
58457
58678
|
emitPlateGalleryChanged: (payload) => this.emitPlateGalleryChanged(payload),
|
|
58679
|
+
onLiveHierarchy: (input) => this.requestTrackHierarchy(input),
|
|
58458
58680
|
logger: logger.child("PlateRecognizer")
|
|
58459
58681
|
});
|
|
58460
58682
|
this.plateRecognizer.refreshGallery();
|
|
@@ -58810,13 +59032,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
58810
59032
|
buildSummaryPorts(stores) {
|
|
58811
59033
|
return {
|
|
58812
59034
|
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
59035
|
const windowTracks = await stores.trackStore.queryHistorical({
|
|
58821
59036
|
deviceId,
|
|
58822
59037
|
since: sinceMs,
|
|
@@ -58824,33 +59039,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
58824
59039
|
projection: "slim",
|
|
58825
59040
|
limit: Math.max(limit, 2e3)
|
|
58826
59041
|
});
|
|
58827
|
-
const trackById = new Map(windowTracks.map((t) => [t.trackId, t]));
|
|
58828
59042
|
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;
|
|
59043
|
+
return windowTracks.map((t) => ({
|
|
59044
|
+
trackId: t.trackId,
|
|
59045
|
+
deviceId,
|
|
59046
|
+
className: t.className,
|
|
59047
|
+
firstSeen: t.firstSeen,
|
|
59048
|
+
lastSeen: t.lastSeen,
|
|
59049
|
+
zones: [...t.zonesVisited],
|
|
59050
|
+
...t.importance !== void 0 ? { importance: t.importance } : {},
|
|
59051
|
+
...t.classes !== void 0 && t.classes.length > 0 ? { classes: t.classes } : {},
|
|
59052
|
+
...t.closedReason === "stationary-promotion" ? { stationary: true } : {},
|
|
59053
|
+
confirmed: t.observations !== void 0 && t.observations >= minHits
|
|
59054
|
+
}));
|
|
58854
59055
|
},
|
|
58855
59056
|
listMediaInfo: async (id) => {
|
|
58856
59057
|
const group = await stores.groupStore.get(id);
|
|
@@ -59845,6 +60046,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
59845
60046
|
refreshGallery: () => {
|
|
59846
60047
|
this.plateRecognizer?.refreshGallery();
|
|
59847
60048
|
},
|
|
60049
|
+
eventMediaBaseUrl: () => this.eventMediaBaseUrl,
|
|
59848
60050
|
emitPlateGalleryChanged: (payload) => this.emitPlateGalleryChanged(payload)
|
|
59849
60051
|
});
|
|
59850
60052
|
return [
|
|
@@ -60333,6 +60535,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
60333
60535
|
deviceId,
|
|
60334
60536
|
tracked: result.tracked,
|
|
60335
60537
|
packageClasses,
|
|
60538
|
+
packageZoneIds: collectPackageZoneIds(proxy?.state.zoneRules.value?.package ?? []),
|
|
60336
60539
|
settings: packageSettings,
|
|
60337
60540
|
frameWidth: result.frameWidth,
|
|
60338
60541
|
frameHeight: result.frameHeight,
|
|
@@ -60900,7 +61103,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
60900
61103
|
if (!first) return;
|
|
60901
61104
|
const lifeMs = track.lastSeen - track.firstSeen;
|
|
60902
61105
|
const moved = netDisplacementPx(track.positions ?? []);
|
|
60903
|
-
if (!isShortMotionlessTrack(lifeMs, moved)) {
|
|
61106
|
+
if (!isShortMotionlessTrack(lifeMs, moved, track.className)) {
|
|
60904
61107
|
this.phantomCells?.noteMovingClose({
|
|
60905
61108
|
deviceId: track.deviceId,
|
|
60906
61109
|
className: track.className,
|
|
@@ -61018,6 +61221,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
61018
61221
|
}, d.score, d.label, frame.timestamp);
|
|
61019
61222
|
if (this.plateRecognizer) {
|
|
61020
61223
|
const mediaSettings = await this.resolveDeviceMediaSettings(deviceId);
|
|
61224
|
+
const body = this.residents.bestSeenBbox(trackId);
|
|
61021
61225
|
await this.plateRecognizer.observePlateRead({
|
|
61022
61226
|
deviceId,
|
|
61023
61227
|
trackId,
|
|
@@ -61033,7 +61237,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
61033
61237
|
frameWidth: frame.frameWidth,
|
|
61034
61238
|
frameHeight: frame.frameHeight,
|
|
61035
61239
|
cropPadding: mediaSettings.cropPadding,
|
|
61036
|
-
...frame.frameHandle !== void 0 ? { frameHandle: frame.frameHandle } : {}
|
|
61240
|
+
...frame.frameHandle !== void 0 ? { frameHandle: frame.frameHandle } : {},
|
|
61241
|
+
...body !== void 0 ? { bodyBbox: {
|
|
61242
|
+
x: body.x,
|
|
61243
|
+
y: body.y,
|
|
61244
|
+
w: body.w,
|
|
61245
|
+
h: body.h
|
|
61246
|
+
} } : {}
|
|
61037
61247
|
});
|
|
61038
61248
|
}
|
|
61039
61249
|
}
|
|
@@ -61334,6 +61544,51 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
61334
61544
|
}
|
|
61335
61545
|
}).catch(() => {})));
|
|
61336
61546
|
}
|
|
61547
|
+
/**
|
|
61548
|
+
* A face or plate crop just landed from a still-live handle. Fill the
|
|
61549
|
+
* missing parent rungs — person/vehicle thumbnail (display crop, same
|
|
61550
|
+
* surface that produced the detail) and native keyFrame — so Events never
|
|
61551
|
+
* has a child crop without a body and a scene.
|
|
61552
|
+
*/
|
|
61553
|
+
requestTrackHierarchy(input) {
|
|
61554
|
+
ensureTrackHierarchyMedia({
|
|
61555
|
+
isThumbnailLanded: (trackId) => this.residents.isThumbnailLanded(trackId),
|
|
61556
|
+
isKeyFrameLanded: (trackId) => this.residents.keyFrameCapture(trackId).landed,
|
|
61557
|
+
captureBodyThumbnail: (req) => this.captureHierarchyBodyThumbnail(req),
|
|
61558
|
+
captureKeyFrame: async (req) => {
|
|
61559
|
+
await this.persistKeyFrames(req.deviceId, req.timestamp, [{
|
|
61560
|
+
trackId: req.trackId,
|
|
61561
|
+
retry: false
|
|
61562
|
+
}], req.frameHandle);
|
|
61563
|
+
}
|
|
61564
|
+
}, input);
|
|
61565
|
+
}
|
|
61566
|
+
async captureHierarchyBodyThumbnail(input) {
|
|
61567
|
+
const displayCrop = this.captureDisplayCropFn;
|
|
61568
|
+
const mediaStore = this.mediaStore;
|
|
61569
|
+
if (!displayCrop || !mediaStore) return;
|
|
61570
|
+
await this.captureScheduler.request({
|
|
61571
|
+
deviceId: input.deviceId,
|
|
61572
|
+
trackId: input.trackId,
|
|
61573
|
+
kind: "thumbnail",
|
|
61574
|
+
coalesceKey: captureCoalesceKey(input.deviceId, "thumbnail", `${input.trackId}:hierarchy`),
|
|
61575
|
+
staleAfterMs: ENRICHMENT_CAPTURE_STALE_MS,
|
|
61576
|
+
exec: async () => {
|
|
61577
|
+
if (this.residents.isThumbnailLanded(input.trackId)) return;
|
|
61578
|
+
const jpeg = await displayCrop(input.frameHandle, input.bodyBbox, input.frameWidth, input.frameHeight, input.cropPadding, input.trackId);
|
|
61579
|
+
if (!jpeg) return;
|
|
61580
|
+
await mediaStore.put({
|
|
61581
|
+
deviceId: input.deviceId,
|
|
61582
|
+
ownerKind: "track",
|
|
61583
|
+
ownerId: input.trackId,
|
|
61584
|
+
kind: "thumbnail",
|
|
61585
|
+
timestamp: input.timestamp,
|
|
61586
|
+
data: jpeg
|
|
61587
|
+
});
|
|
61588
|
+
this.residents.markThumbnailLanded(input.deviceId, input.trackId);
|
|
61589
|
+
}
|
|
61590
|
+
}).catch(() => {});
|
|
61591
|
+
}
|
|
61337
61592
|
/** Attribution-time SEMANTIC best-shot promotion (D-semantic-bestshot,
|
|
61338
61593
|
* 2026-08-21; see `pipeline/semantic-promotion.ts`): offer a frame from
|
|
61339
61594
|
* which a recognition SUCCEEDED to the best-frame ranking with the
|
|
@@ -62047,25 +62302,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
62047
62302
|
}
|
|
62048
62303
|
/** Hub-only: assign this sighting in RAM. First activity on a camera
|
|
62049
62304
|
* 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
|
-
}
|
|
62305
|
+
enqueueLiveGroup(track, now) {}
|
|
62069
62306
|
/** Unified retention (Phase 1): earliest retained footage per device — the
|
|
62070
62307
|
* follow-recordings cutoff source. Cached 10 min: availability answers in
|
|
62071
62308
|
* ~70 ms but the sweep asks once per device per pass. `null` = no footage
|
|
@@ -62511,7 +62748,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
62511
62748
|
runPackageStillness(input) {
|
|
62512
62749
|
if (this.packageDropDetector === null) return;
|
|
62513
62750
|
const tags = { deviceId: input.deviceId };
|
|
62514
|
-
for (const t of input.tracked)
|
|
62751
|
+
for (const t of input.tracked) {
|
|
62752
|
+
if (input.packageClasses.has(t.className)) continue;
|
|
62753
|
+
if (!isDelivererInPackageZone(t.zones, input.packageZoneIds)) continue;
|
|
62515
62754
|
this.lastNonPackageActivityAt.set(input.deviceId, input.timestamp);
|
|
62516
62755
|
break;
|
|
62517
62756
|
}
|
|
@@ -62558,6 +62797,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
62558
62797
|
frameWidth: input.frameWidth,
|
|
62559
62798
|
frameHeight: input.frameHeight,
|
|
62560
62799
|
timestamp: input.timestamp,
|
|
62800
|
+
minHits: input.settings.packageDropMinFrames,
|
|
62561
62801
|
minDwellMs: input.settings.packageDropDwellSec * 1e3,
|
|
62562
62802
|
activityWindowMs: input.settings.packageDropActivityWindowSec * 1e3,
|
|
62563
62803
|
...lastActivityAt !== void 0 ? { lastActivityAt } : {}
|
|
@@ -62661,9 +62901,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
62661
62901
|
for (const ex of outcome.expired) {
|
|
62662
62902
|
const observedDwellMs = ex.lastSeenAt - ex.firstSeenAt;
|
|
62663
62903
|
const requiredDwellMs = input.settings.packageDropDwellSec * 1e3;
|
|
62904
|
+
const requiredHits = input.settings.packageDropMinFrames;
|
|
62664
62905
|
const reasons = [];
|
|
62665
62906
|
if (!ex.activityBackedAtOpen) reasons.push("no-activity-before-open");
|
|
62666
|
-
if (ex.
|
|
62907
|
+
if (ex.hits < requiredHits) reasons.push("hits");
|
|
62667
62908
|
if (observedDwellMs < requiredDwellMs) reasons.push("dwell");
|
|
62668
62909
|
this.ctx.logger.info("package candidate expired without promotion", {
|
|
62669
62910
|
tags,
|
|
@@ -62671,8 +62912,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
62671
62912
|
candidateId: ex.id,
|
|
62672
62913
|
className: ex.className,
|
|
62673
62914
|
bbox: ex.bbox,
|
|
62674
|
-
|
|
62675
|
-
|
|
62915
|
+
hits: ex.hits,
|
|
62916
|
+
minHits: requiredHits,
|
|
62676
62917
|
observedDwellMs,
|
|
62677
62918
|
requiredDwellMs,
|
|
62678
62919
|
activityBackedAtOpen: ex.activityBackedAtOpen,
|
|
@@ -62853,15 +63094,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
62853
63094
|
* applies to the very next frame even when frames stop briefly
|
|
62854
63095
|
* (e.g. during binding flips).
|
|
62855
63096
|
*
|
|
62856
|
-
* RECONCILE: the push channel behind `subscribe`
|
|
62857
|
-
* via `
|
|
62858
|
-
*
|
|
62859
|
-
*
|
|
62860
|
-
*
|
|
62861
|
-
* telemetry (D8); the durable channel is RPC + reconcile — so each
|
|
62862
|
-
* proxy also refreshes its two slices on a slow timer. `refresh()`
|
|
62863
|
-
* round-trips `deviceState.getCapSlice` and fans out through the SAME
|
|
62864
|
-
* subscribe callbacks above, so a zone edit lands within one interval.
|
|
63097
|
+
* RECONCILE: the push channel behind `subscribe` is `device.state-changed`
|
|
63098
|
+
* via the addon's `ctx.eventBus` (`createEventBusSliceSource`). That
|
|
63099
|
+
* declares UDS interest so the parent actually fans the slice here. Events
|
|
63100
|
+
* remain lossy (D8); `refresh()` round-trips `deviceState.getCapSlice` on
|
|
63101
|
+
* this timer as the backstop.
|
|
62865
63102
|
*/
|
|
62866
63103
|
async ensureProxy(deviceId) {
|
|
62867
63104
|
const cached = this.proxies.get(deviceId);
|
|
@@ -62934,6 +63171,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
62934
63171
|
async listRecentTracks(input) {
|
|
62935
63172
|
return this.queryFacade.listRecentTracks(input);
|
|
62936
63173
|
}
|
|
63174
|
+
async listGroups(input) {
|
|
63175
|
+
return {
|
|
63176
|
+
groups: [],
|
|
63177
|
+
nextCursor: null
|
|
63178
|
+
};
|
|
63179
|
+
}
|
|
63180
|
+
async getGroup(input) {
|
|
63181
|
+
return null;
|
|
63182
|
+
}
|
|
62937
63183
|
/**
|
|
62938
63184
|
* Every event kind the device can produce: built-ins (motion + audio),
|
|
62939
63185
|
* detection classes actually observed on the device, and sensor kinds
|
|
@@ -64078,33 +64324,38 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
64078
64324
|
buildFaceRescoreActionHandlers() {
|
|
64079
64325
|
return { "face.rescoreTrack": (input) => this.rescoreFaceForTrack(input) };
|
|
64080
64326
|
}
|
|
64081
|
-
|
|
64082
|
-
const
|
|
64083
|
-
|
|
64084
|
-
|
|
64085
|
-
|
|
64086
|
-
|
|
64087
|
-
|
|
64088
|
-
|
|
64089
|
-
|
|
64090
|
-
|
|
64091
|
-
|
|
64092
|
-
|
|
64093
|
-
|
|
64327
|
+
groupQueryPorts() {
|
|
64328
|
+
const groupStore = this.groupStore;
|
|
64329
|
+
if (groupStore === null) return null;
|
|
64330
|
+
return {
|
|
64331
|
+
groupStore,
|
|
64332
|
+
loadTracks: async (trackIds) => {
|
|
64333
|
+
const map = /* @__PURE__ */ new Map();
|
|
64334
|
+
const tracks = this.trackStore;
|
|
64335
|
+
if (!tracks || trackIds.length === 0) return map;
|
|
64336
|
+
const missing = [];
|
|
64337
|
+
for (const id of trackIds) {
|
|
64338
|
+
const active = tracks.getActiveByTrack(id);
|
|
64339
|
+
if (active !== null) map.set(id, trackToLite(active));
|
|
64340
|
+
else missing.push(id);
|
|
64341
|
+
}
|
|
64342
|
+
if (missing.length === 0) return map;
|
|
64343
|
+
for (const persisted of await tracks.getPersistedByTrackIds(missing)) map.set(persisted.trackId, trackToLite(persisted));
|
|
64344
|
+
return map;
|
|
64345
|
+
},
|
|
64346
|
+
eventMediaBaseUrl: () => this.eventMediaBaseUrl
|
|
64094
64347
|
};
|
|
64348
|
+
}
|
|
64349
|
+
buildGroupActionHandlers() {
|
|
64095
64350
|
return {
|
|
64096
64351
|
"analytics.listGroups": async (input) => {
|
|
64097
|
-
|
|
64098
|
-
if (ready === null) return {
|
|
64352
|
+
return {
|
|
64099
64353
|
groups: [],
|
|
64100
64354
|
hasMore: false
|
|
64101
64355
|
};
|
|
64102
|
-
return listGroupRecords(ready, input);
|
|
64103
64356
|
},
|
|
64104
64357
|
"analytics.getGroup": async (input) => {
|
|
64105
|
-
|
|
64106
|
-
if (ready === null) return { group: null };
|
|
64107
|
-
return { group: await getGroupDetail(ready, input.groupId) };
|
|
64358
|
+
return { group: null };
|
|
64108
64359
|
}
|
|
64109
64360
|
};
|
|
64110
64361
|
}
|
|
@@ -64778,14 +65029,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
64778
65029
|
async readEventThumbnail(id, preferKind) {
|
|
64779
65030
|
const eventFiles = await (this.mediaStore?.listByOwner("event", id) ?? Promise.resolve([]));
|
|
64780
65031
|
if (preferKind !== void 0 && preferKind.length > 0) {
|
|
64781
|
-
const
|
|
64782
|
-
|
|
64783
|
-
|
|
64784
|
-
|
|
64785
|
-
|
|
64786
|
-
...trackFiles,
|
|
64787
|
-
...owningTrackFiles
|
|
64788
|
-
], (files) => pickCleanMedia(files, preferKind));
|
|
65032
|
+
const layers = await collectCropForcedMedia({
|
|
65033
|
+
listByOwner: (ownerKind, ownerId) => this.mediaStore?.listByOwner(ownerKind, ownerId) ?? Promise.resolve([]),
|
|
65034
|
+
getTrackIdForEvent: (eventId) => this.eventStore?.getTrackIdForEvent(eventId) ?? Promise.resolve(null)
|
|
65035
|
+
}, id);
|
|
65036
|
+
const chosen = resolveCropForcedMedia(layers.bodyFiles, (files) => pickCleanMedia(files, preferKind), layers.detailFiles);
|
|
64789
65037
|
if (!chosen) return null;
|
|
64790
65038
|
return {
|
|
64791
65039
|
bytes: Buffer.from(chosen.base64, "base64"),
|