@camstack/addon-post-analysis 1.2.181 → 1.2.183
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-DEAreP8H.mjs → dist-DvmLRRIq.mjs} +13 -4
- package/dist/{dist-BB70nZUm.js → dist-_fedHYU3.js} +13 -4
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DAWsjr0n.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-ffBUTS31.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-CKa5HUFy.mjs → hostInit-x1-ijfAu.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +852 -34
- package/dist/pipeline-analytics/index.mjs +906 -88
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as audioModeOf, A as NcSnoozeSchema, At as BaseAddon, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as boolean, C as NcConditionDescriptorSchema, Ct as sceneMonitorCapability, D as NcRuleTargetSchema, Dt as videoclipsCapability, E as NcRuleSchema, Et as vectorDimFromBase64, Ft as isDeviceScopedCap, G as TimelapseRulePatchSchema, Gt as partialRecord, H as SceneMonitorSchema, Ht as literal, I as RECORDING_EXPORT_MAX_READ_BYTES, It as nodePin, J as VISIT_MERGE_GAP_MS, Jt as unknown, K as TimelapseRuleSchema, Kt as record, L as RetrainStatusSchema, Lt as sleep$1, M as NcSystemEventKindSchema, Mt as DeviceType, N as NcTaxonomySchema, Nt as createEvent, O as NcScheduleSchema, Ot as zoneAnalyticsCapability, P as OpsLogEntrySchema, Pt as hydrateSchema, Q as audioMetricsCapability, Rt as _enum, S as NC_TAXONOMY, T as NcRulePatchSchema, Tt as systemEventFilterApplies, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as number, V as SCENE_DIVERGED, Vt as discriminatedUnion, W as TimelapseRuleInputSchema, Wt as object, X as alarmPanelCapability, Y as addonWidgetsSourceCapability, Yt as EventCategory, Z as assertTimelapseCadences, _ as MediaFileKindEnum, _t as pickClusterStepModels, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as readDeviceStateFrom, c as DETECTION_MACRO_CLASSES, ct as faceGalleryCapability, d as EVENT_KIND_BY_CAP, dt as isDetectionMacroClass, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isScheduleActive, g as MACRO_LABELS, h as LabelAttributionSchema, ht as notificationRulesCapability, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as CamProfileSchema, k as NcSnoozeInputSchema, kt as errMsg, lt as failureContributionCapability, m as FailureCounters, mt as kebabToCamel, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isSourceCap, q as TrackSourceSchema, qt as string, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, v as NC_ALARM_SYSTEM_EVENT_KINDS, vt as pipelineAnalyticsCapability, w as NcRuleInputSchema, wt as subKindsOf, xt as readTimelapseGeneratedAt, y as NC_CONDITION_CATALOG, yt as plateGalleryCapability, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as array } from "../dist-
|
|
1
|
+
import { $ as audioModeOf, A as NcSnoozeSchema, At as BaseAddon, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as boolean, C as NcConditionDescriptorSchema, Ct as sceneMonitorCapability, D as NcRuleTargetSchema, Dt as videoclipsCapability, E as NcRuleSchema, Et as vectorDimFromBase64, Ft as isDeviceScopedCap, G as TimelapseRulePatchSchema, Gt as partialRecord, H as SceneMonitorSchema, Ht as literal, I as RECORDING_EXPORT_MAX_READ_BYTES, It as nodePin, J as VISIT_MERGE_GAP_MS, Jt as unknown, K as TimelapseRuleSchema, Kt as record, L as RetrainStatusSchema, Lt as sleep$1, M as NcSystemEventKindSchema, Mt as DeviceType, N as NcTaxonomySchema, Nt as createEvent, O as NcScheduleSchema, Ot as zoneAnalyticsCapability, P as OpsLogEntrySchema, Pt as hydrateSchema, Q as audioMetricsCapability, Rt as _enum, S as NC_TAXONOMY, T as NcRulePatchSchema, Tt as systemEventFilterApplies, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as number, V as SCENE_DIVERGED, Vt as discriminatedUnion, W as TimelapseRuleInputSchema, Wt as object, X as alarmPanelCapability, Y as addonWidgetsSourceCapability, Yt as EventCategory, Z as assertTimelapseCadences, _ as MediaFileKindEnum, _t as pickClusterStepModels, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as readDeviceStateFrom, c as DETECTION_MACRO_CLASSES, ct as faceGalleryCapability, d as EVENT_KIND_BY_CAP, dt as isDetectionMacroClass, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isScheduleActive, g as MACRO_LABELS, h as LabelAttributionSchema, ht as notificationRulesCapability, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as CamProfileSchema, k as NcSnoozeInputSchema, kt as errMsg$1, lt as failureContributionCapability, m as FailureCounters, mt as kebabToCamel, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isSourceCap, q as TrackSourceSchema, qt as string, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, v as NC_ALARM_SYSTEM_EVENT_KINDS, vt as pipelineAnalyticsCapability, w as NcRuleInputSchema, wt as subKindsOf, xt as readTimelapseGeneratedAt, y as NC_CONDITION_CATALOG, yt as plateGalleryCapability, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as array } from "../dist-DvmLRRIq.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { promises } from "node:fs";
|
|
@@ -27813,6 +27813,7 @@ var StoragePressureTracker = class {
|
|
|
27813
27813
|
var OWNER_KINDS = [
|
|
27814
27814
|
"event",
|
|
27815
27815
|
"track",
|
|
27816
|
+
"summary",
|
|
27816
27817
|
"face",
|
|
27817
27818
|
"identity",
|
|
27818
27819
|
"plate",
|
|
@@ -28396,7 +28397,8 @@ var MediaStore = class {
|
|
|
28396
28397
|
relativePath: path
|
|
28397
28398
|
})).toString("base64"),
|
|
28398
28399
|
sizeBytes,
|
|
28399
|
-
timestamp
|
|
28400
|
+
timestamp,
|
|
28401
|
+
deviceId: Number(data["deviceId"])
|
|
28400
28402
|
};
|
|
28401
28403
|
} catch {
|
|
28402
28404
|
return null;
|
|
@@ -30670,7 +30672,7 @@ async function mapWithConcurrency(items, limit, mapper) {
|
|
|
30670
30672
|
* all-signals-maxed track scores exactly 1.0 (the clamp is then a no-op) and an
|
|
30671
30673
|
* all-zero track scores 0.
|
|
30672
30674
|
*/
|
|
30673
|
-
var WEIGHT_CONFIDENCE = .3;
|
|
30675
|
+
var WEIGHT_CONFIDENCE$1 = .3;
|
|
30674
30676
|
var WEIGHT_CLASS = .2;
|
|
30675
30677
|
var WEIGHT_DWELL = .2;
|
|
30676
30678
|
var WEIGHT_PROXIMITY = .12;
|
|
@@ -30708,7 +30710,7 @@ var ANIMAL_CLASSES = new Set([
|
|
|
30708
30710
|
"cat"
|
|
30709
30711
|
]);
|
|
30710
30712
|
/** Clamp to [0,1]. */
|
|
30711
|
-
function clamp01$
|
|
30713
|
+
function clamp01$2(x) {
|
|
30712
30714
|
if (Number.isNaN(x)) return 0;
|
|
30713
30715
|
if (x < 0) return 0;
|
|
30714
30716
|
if (x > 1) return 1;
|
|
@@ -30742,7 +30744,7 @@ function computeImportance(input) {
|
|
|
30742
30744
|
const terms = [
|
|
30743
30745
|
{
|
|
30744
30746
|
reason: "confidence",
|
|
30745
|
-
value: WEIGHT_CONFIDENCE * clamp01$
|
|
30747
|
+
value: WEIGHT_CONFIDENCE$1 * clamp01$2(input.peakConfidence)
|
|
30746
30748
|
},
|
|
30747
30749
|
{
|
|
30748
30750
|
reason: "class",
|
|
@@ -30750,15 +30752,15 @@ function computeImportance(input) {
|
|
|
30750
30752
|
},
|
|
30751
30753
|
{
|
|
30752
30754
|
reason: "dwell",
|
|
30753
|
-
value: WEIGHT_DWELL * clamp01$
|
|
30755
|
+
value: WEIGHT_DWELL * clamp01$2(input.durationMs / DWELL_FULL_MS)
|
|
30754
30756
|
},
|
|
30755
30757
|
{
|
|
30756
30758
|
reason: "proximity",
|
|
30757
|
-
value: WEIGHT_PROXIMITY * clamp01$
|
|
30759
|
+
value: WEIGHT_PROXIMITY * clamp01$2(input.peakBboxAreaFrac / SIZE_FULL)
|
|
30758
30760
|
},
|
|
30759
30761
|
{
|
|
30760
30762
|
reason: "travel",
|
|
30761
|
-
value: WEIGHT_TRAVEL * clamp01$
|
|
30763
|
+
value: WEIGHT_TRAVEL * clamp01$2(input.totalDistance / 2)
|
|
30762
30764
|
},
|
|
30763
30765
|
{
|
|
30764
30766
|
reason: "zone",
|
|
@@ -30777,11 +30779,11 @@ function computeImportance(input) {
|
|
|
30777
30779
|
}
|
|
30778
30780
|
const identified = input.identityLabel !== void 0 && input.identityLabel.length > 0;
|
|
30779
30781
|
if (input.netDisplacementFrac !== void 0 && input.pathSpanFrac !== void 0 && input.netDisplacementFrac < .02 && input.pathSpanFrac < .04 && !identified) return {
|
|
30780
|
-
importance: clamp01$
|
|
30782
|
+
importance: clamp01$2(sum * STATIC_IMPORTANCE_MULTIPLIER),
|
|
30781
30783
|
reason: best.reason
|
|
30782
30784
|
};
|
|
30783
30785
|
return {
|
|
30784
|
-
importance: clamp01$
|
|
30786
|
+
importance: clamp01$2(sum),
|
|
30785
30787
|
reason: best.reason
|
|
30786
30788
|
};
|
|
30787
30789
|
}
|
|
@@ -33479,7 +33481,7 @@ function isEdgeAwareNewBest(current, candidate, hysteresis) {
|
|
|
33479
33481
|
* it, so it can be loose enough to see the near misses.
|
|
33480
33482
|
*/
|
|
33481
33483
|
var CO_MOVING_MIN_CONTAINMENT = .6;
|
|
33482
|
-
function intersectionArea$
|
|
33484
|
+
function intersectionArea$4(a, b) {
|
|
33483
33485
|
return Math.max(0, Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x)) * Math.max(0, Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y));
|
|
33484
33486
|
}
|
|
33485
33487
|
/**
|
|
@@ -33499,7 +33501,7 @@ function findCoMovingPairs(born, live, minContainment = CO_MOVING_MIN_CONTAINMEN
|
|
|
33499
33501
|
if (o.className === b.className) continue;
|
|
33500
33502
|
const areaO = o.bbox.w * o.bbox.h;
|
|
33501
33503
|
if (areaO <= 0) continue;
|
|
33502
|
-
const inter = intersectionArea$
|
|
33504
|
+
const inter = intersectionArea$4(b.bbox, o.bbox);
|
|
33503
33505
|
if (inter <= 0) continue;
|
|
33504
33506
|
const union = areaB + areaO - inter;
|
|
33505
33507
|
const containment = inter / Math.min(areaB, areaO);
|
|
@@ -33931,7 +33933,7 @@ function pairRiders(detections) {
|
|
|
33931
33933
|
* diagonal = 2.085) with the same ~10% headroom the old 1.35 gave it.
|
|
33932
33934
|
*/
|
|
33933
33935
|
var GROWTH_CENTROID_FACTOR = 2.3;
|
|
33934
|
-
function intersectionArea$
|
|
33936
|
+
function intersectionArea$3(a, b) {
|
|
33935
33937
|
const x1 = Math.max(a.x, b.x);
|
|
33936
33938
|
const y1 = Math.max(a.y, b.y);
|
|
33937
33939
|
const x2 = Math.min(a.x + a.w, b.x + b.w);
|
|
@@ -33969,7 +33971,7 @@ function growthAdjacentMatch(track, det, ctx) {
|
|
|
33969
33971
|
const larger = Math.max(trackArea, detArea);
|
|
33970
33972
|
const smaller = Math.min(trackArea, detArea);
|
|
33971
33973
|
if (larger / smaller < 1.5) return false;
|
|
33972
|
-
if (intersectionArea$
|
|
33974
|
+
if (intersectionArea$3(track, det) / smaller >= .5) return false;
|
|
33973
33975
|
const tc = bboxCentroid(track);
|
|
33974
33976
|
const dc = bboxCentroid(det);
|
|
33975
33977
|
return Math.hypot(tc.x - dc.x, tc.y - dc.y) <= GROWTH_CENTROID_FACTOR * Math.hypot(track.w, track.h);
|
|
@@ -35955,6 +35957,7 @@ var EMPTY_CASCADE_TALLY = {
|
|
|
35955
35957
|
faces: 0,
|
|
35956
35958
|
plates: 0,
|
|
35957
35959
|
embeddings: 0,
|
|
35960
|
+
summaries: 0,
|
|
35958
35961
|
bytesReclaimed: 0
|
|
35959
35962
|
};
|
|
35960
35963
|
/** Fold a leaf store's receipts into a running tally. */
|
|
@@ -35998,6 +36001,13 @@ function addLeafCounts(tally, counts) {
|
|
|
35998
36001
|
bytesReclaimed
|
|
35999
36002
|
};
|
|
36000
36003
|
break;
|
|
36004
|
+
case "summaries":
|
|
36005
|
+
next = {
|
|
36006
|
+
...next,
|
|
36007
|
+
summaries: next.summaries + c.rows,
|
|
36008
|
+
bytesReclaimed
|
|
36009
|
+
};
|
|
36010
|
+
break;
|
|
36001
36011
|
}
|
|
36002
36012
|
}
|
|
36003
36013
|
return next;
|
|
@@ -36011,6 +36021,7 @@ function mergeCascadeTallies(a, b) {
|
|
|
36011
36021
|
faces: a.faces + b.faces,
|
|
36012
36022
|
plates: a.plates + b.plates,
|
|
36013
36023
|
embeddings: a.embeddings + b.embeddings,
|
|
36024
|
+
summaries: a.summaries + b.summaries,
|
|
36014
36025
|
bytesReclaimed: a.bytesReclaimed + b.bytesReclaimed
|
|
36015
36026
|
};
|
|
36016
36027
|
}
|
|
@@ -36073,6 +36084,7 @@ function absentCascadeLegs(stores) {
|
|
|
36073
36084
|
if (!stores.faceStore) absent.push("faces");
|
|
36074
36085
|
if (!stores.plateStore) absent.push("plates");
|
|
36075
36086
|
if (!stores.objectEmbeddingStore) absent.push("embeddings");
|
|
36087
|
+
if (!stores.summaryStore) absent.push("summaries");
|
|
36076
36088
|
return absent;
|
|
36077
36089
|
}
|
|
36078
36090
|
function buildAnalyticsCascadeRegistry(stores) {
|
|
@@ -36080,6 +36092,7 @@ function buildAnalyticsCascadeRegistry(stores) {
|
|
|
36080
36092
|
if (stores.faceStore) registry.push(stores.faceStore);
|
|
36081
36093
|
if (stores.plateStore) registry.push(stores.plateStore);
|
|
36082
36094
|
if (stores.objectEmbeddingStore) registry.push(stores.objectEmbeddingStore);
|
|
36095
|
+
if (stores.summaryStore) registry.push(stores.summaryStore);
|
|
36083
36096
|
return registry;
|
|
36084
36097
|
}
|
|
36085
36098
|
/**
|
|
@@ -46719,6 +46732,15 @@ var CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
|
46719
46732
|
var THUMB_DEFAULT_SIZE = 160;
|
|
46720
46733
|
var THUMB_MIN_SIZE = 64;
|
|
46721
46734
|
var THUMB_MAX_SIZE = 320;
|
|
46735
|
+
/** Set by the HUB for a `csv_*` share token admitted on this plane; never by a client. */
|
|
46736
|
+
var SHARE_DEVICES_HEADER = "x-camstack-share-devices";
|
|
46737
|
+
/** The device ids a share scope allows, or `null` when the request carries no scope. */
|
|
46738
|
+
function shareScopeDevices(headers) {
|
|
46739
|
+
const raw = headers[SHARE_DEVICES_HEADER];
|
|
46740
|
+
const value = Array.isArray(raw) ? raw.join(",") : raw;
|
|
46741
|
+
if (value === void 0) return null;
|
|
46742
|
+
return new Set(value.split(",").map((part) => Number.parseInt(part.trim(), 10)).filter((id) => Number.isInteger(id) && id >= 0));
|
|
46743
|
+
}
|
|
46722
46744
|
/**
|
|
46723
46745
|
* Parse the `?kind=…` query into a preferred stored media kind. Returns null
|
|
46724
46746
|
* when unset. The value is a free-form kind token (e.g. `crop`); the resolver
|
|
@@ -46791,6 +46813,14 @@ function createEventMediaHandler(deps) {
|
|
|
46791
46813
|
res.writeHead(404, { "server-timing": `resolve;dur=${(performance.now() - resolveStartedAt).toFixed(1)}` }).end();
|
|
46792
46814
|
return;
|
|
46793
46815
|
}
|
|
46816
|
+
const scope = shareScopeDevices(req.headers);
|
|
46817
|
+
if (scope !== null) {
|
|
46818
|
+
const owner = media.deviceId ?? null;
|
|
46819
|
+
if (owner === null || !scope.has(owner)) {
|
|
46820
|
+
res.writeHead(403).end();
|
|
46821
|
+
return;
|
|
46822
|
+
}
|
|
46823
|
+
}
|
|
46794
46824
|
const etag = "\"" + media.key + "\"";
|
|
46795
46825
|
const serverTiming = `resolve;dur=${(performance.now() - resolveStartedAt).toFixed(1)}`;
|
|
46796
46826
|
if (req.headers["if-none-match"] === etag) {
|
|
@@ -48261,7 +48291,7 @@ var RetrainAnnotationStore = class {
|
|
|
48261
48291
|
};
|
|
48262
48292
|
//#endregion
|
|
48263
48293
|
//#region src/pipeline-analytics/retrain/retrain-geometry.ts
|
|
48264
|
-
function clamp01(v) {
|
|
48294
|
+
function clamp01$1(v) {
|
|
48265
48295
|
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
48266
48296
|
}
|
|
48267
48297
|
/** Is this a usable rectangle at all? A zero-area box is not a subject. */
|
|
@@ -48337,10 +48367,10 @@ function deriveZoneCropRect(bbox, zone) {
|
|
|
48337
48367
|
function deriveSubjectCropRect(bbox, padding) {
|
|
48338
48368
|
if (!isUsableRect(bbox)) return null;
|
|
48339
48369
|
const pad = Number.isFinite(padding) && padding > 0 ? padding : 0;
|
|
48340
|
-
const cropX = clamp01(bbox.x - bbox.w * pad);
|
|
48341
|
-
const cropY = clamp01(bbox.y - bbox.h * pad);
|
|
48342
|
-
const cropRight = clamp01(bbox.x + bbox.w * (1 + pad));
|
|
48343
|
-
const cropBottom = clamp01(bbox.y + bbox.h * (1 + pad));
|
|
48370
|
+
const cropX = clamp01$1(bbox.x - bbox.w * pad);
|
|
48371
|
+
const cropY = clamp01$1(bbox.y - bbox.h * pad);
|
|
48372
|
+
const cropRight = clamp01$1(bbox.x + bbox.w * (1 + pad));
|
|
48373
|
+
const cropBottom = clamp01$1(bbox.y + bbox.h * (1 + pad));
|
|
48344
48374
|
const cropW = cropRight - cropX;
|
|
48345
48375
|
const cropH = cropBottom - cropY;
|
|
48346
48376
|
if (!(cropW > 0) || !(cropH > 0)) return null;
|
|
@@ -51714,7 +51744,7 @@ async function persistTrackKeyFrame(deps, input) {
|
|
|
51714
51744
|
...drop,
|
|
51715
51745
|
meta: {
|
|
51716
51746
|
...drop.meta,
|
|
51717
|
-
error: errMsg(err)
|
|
51747
|
+
error: errMsg$1(err)
|
|
51718
51748
|
}
|
|
51719
51749
|
});
|
|
51720
51750
|
}
|
|
@@ -51723,7 +51753,7 @@ async function persistTrackKeyFrame(deps, input) {
|
|
|
51723
51753
|
keyFrameKey
|
|
51724
51754
|
};
|
|
51725
51755
|
} catch (err) {
|
|
51726
|
-
const error = errMsg(err);
|
|
51756
|
+
const error = errMsg$1(err);
|
|
51727
51757
|
deps.logger.warn("key-frame capture failed", {
|
|
51728
51758
|
...drop,
|
|
51729
51759
|
meta: {
|
|
@@ -52591,7 +52621,7 @@ function isSceneSizedBox(bbox, frameWidth, frameHeight) {
|
|
|
52591
52621
|
const frame = frameWidth * frameHeight;
|
|
52592
52622
|
return frame > 0 && bbox.w * bbox.h / frame >= .55;
|
|
52593
52623
|
}
|
|
52594
|
-
function intersectionArea$
|
|
52624
|
+
function intersectionArea$2(a, b) {
|
|
52595
52625
|
const ix1 = Math.max(a.x, b.x);
|
|
52596
52626
|
const iy1 = Math.max(a.y, b.y);
|
|
52597
52627
|
const ix2 = Math.min(a.x + a.w, b.x + b.w);
|
|
@@ -52604,7 +52634,7 @@ function detectionSwallowsEntry(det, parked) {
|
|
|
52604
52634
|
const detArea = det.w * det.h;
|
|
52605
52635
|
if (entryArea <= 0 || detArea < entryArea * 1.35) return false;
|
|
52606
52636
|
if (!centroidInside(det, centroid(parked))) return false;
|
|
52607
|
-
return intersectionArea$
|
|
52637
|
+
return intersectionArea$2(parked, det) / entryArea >= SWALLOW_MIN_COVERAGE;
|
|
52608
52638
|
}
|
|
52609
52639
|
function parkedEntryHoldsDetection(entry, detBbox, config) {
|
|
52610
52640
|
const overlap = iou(entry.bbox, detBbox);
|
|
@@ -53436,7 +53466,7 @@ var VEHICLE_CLASSES = [
|
|
|
53436
53466
|
"vehicle"
|
|
53437
53467
|
];
|
|
53438
53468
|
/** Area of the intersection of two boxes. `0` when they do not overlap. */
|
|
53439
|
-
function intersectionArea(a, b) {
|
|
53469
|
+
function intersectionArea$1(a, b) {
|
|
53440
53470
|
const left = Math.max(a.x, b.x);
|
|
53441
53471
|
const right = Math.min(a.x + a.w, b.x + b.w);
|
|
53442
53472
|
const top = Math.max(a.y, b.y);
|
|
@@ -53453,7 +53483,7 @@ function intersectionArea(a, b) {
|
|
|
53453
53483
|
function containedFraction(person, vehicle) {
|
|
53454
53484
|
const area = person.w * person.h;
|
|
53455
53485
|
if (area <= 0) return 0;
|
|
53456
|
-
return intersectionArea(person, vehicle) / area;
|
|
53486
|
+
return intersectionArea$1(person, vehicle) / area;
|
|
53457
53487
|
}
|
|
53458
53488
|
/**
|
|
53459
53489
|
* Is this person box contained in any vehicle box in the same frame?
|
|
@@ -56240,6 +56270,359 @@ async function reconcileGallerySamples(deps, options) {
|
|
|
56240
56270
|
stoppedByCeiling
|
|
56241
56271
|
};
|
|
56242
56272
|
}
|
|
56273
|
+
//#endregion
|
|
56274
|
+
//#region src/pipeline-analytics/summary/summary-store.ts
|
|
56275
|
+
var SUMMARIES_COLLECTION = "pipeline-analytics:summaries";
|
|
56276
|
+
var SUMMARY_MEMBERS_COLLECTION = "pipeline-analytics:summary-members";
|
|
56277
|
+
var SUMMARIES_COLUMNS = [
|
|
56278
|
+
{
|
|
56279
|
+
name: "id",
|
|
56280
|
+
type: "TEXT",
|
|
56281
|
+
primaryKey: true,
|
|
56282
|
+
notNull: true
|
|
56283
|
+
},
|
|
56284
|
+
{
|
|
56285
|
+
name: "deviceId",
|
|
56286
|
+
type: "INTEGER",
|
|
56287
|
+
notNull: true
|
|
56288
|
+
},
|
|
56289
|
+
{
|
|
56290
|
+
name: "openedAt",
|
|
56291
|
+
type: "INTEGER",
|
|
56292
|
+
notNull: true
|
|
56293
|
+
},
|
|
56294
|
+
{
|
|
56295
|
+
name: "lastActiveAt",
|
|
56296
|
+
type: "INTEGER",
|
|
56297
|
+
notNull: true
|
|
56298
|
+
},
|
|
56299
|
+
{
|
|
56300
|
+
name: "sealedAt",
|
|
56301
|
+
type: "INTEGER"
|
|
56302
|
+
},
|
|
56303
|
+
(
|
|
56304
|
+
/** `deviceId` while open, NULL once sealed — the unique "one open per camera". */
|
|
56305
|
+
{
|
|
56306
|
+
name: "openDeviceId",
|
|
56307
|
+
type: "INTEGER"
|
|
56308
|
+
}),
|
|
56309
|
+
{
|
|
56310
|
+
name: "memberCount",
|
|
56311
|
+
type: "INTEGER",
|
|
56312
|
+
notNull: true
|
|
56313
|
+
}
|
|
56314
|
+
];
|
|
56315
|
+
var SUMMARIES_INDEXES = [{
|
|
56316
|
+
name: "summaries_open_device",
|
|
56317
|
+
columns: ["openDeviceId"],
|
|
56318
|
+
unique: true
|
|
56319
|
+
}, {
|
|
56320
|
+
name: "summaries_device_opened",
|
|
56321
|
+
columns: ["deviceId", "openedAt"]
|
|
56322
|
+
}];
|
|
56323
|
+
var SUMMARY_MEMBERS_COLUMNS = [
|
|
56324
|
+
{
|
|
56325
|
+
name: "id",
|
|
56326
|
+
type: "TEXT",
|
|
56327
|
+
primaryKey: true,
|
|
56328
|
+
notNull: true
|
|
56329
|
+
},
|
|
56330
|
+
{
|
|
56331
|
+
name: "summaryId",
|
|
56332
|
+
type: "TEXT",
|
|
56333
|
+
notNull: true
|
|
56334
|
+
},
|
|
56335
|
+
{
|
|
56336
|
+
name: "trackId",
|
|
56337
|
+
type: "TEXT",
|
|
56338
|
+
notNull: true
|
|
56339
|
+
},
|
|
56340
|
+
{
|
|
56341
|
+
name: "deviceId",
|
|
56342
|
+
type: "INTEGER",
|
|
56343
|
+
notNull: true
|
|
56344
|
+
},
|
|
56345
|
+
(
|
|
56346
|
+
/** When the track joined — named `timestamp` so the orphan audit's declared-owner scope reads it. */
|
|
56347
|
+
{
|
|
56348
|
+
name: "timestamp",
|
|
56349
|
+
type: "INTEGER",
|
|
56350
|
+
notNull: true
|
|
56351
|
+
})
|
|
56352
|
+
];
|
|
56353
|
+
var SUMMARY_MEMBERS_INDEXES = [{
|
|
56354
|
+
name: "summary_members_summary",
|
|
56355
|
+
columns: ["summaryId"]
|
|
56356
|
+
}, {
|
|
56357
|
+
name: "summary_members_track",
|
|
56358
|
+
columns: ["trackId"]
|
|
56359
|
+
}];
|
|
56360
|
+
function summaryId(deviceId, openedAt) {
|
|
56361
|
+
return `sum-${deviceId}-${openedAt}`;
|
|
56362
|
+
}
|
|
56363
|
+
function memberRowId(summaryIdValue, trackId) {
|
|
56364
|
+
return `${summaryIdValue}:${trackId}`;
|
|
56365
|
+
}
|
|
56366
|
+
function num$1(value, fallback = 0) {
|
|
56367
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
56368
|
+
}
|
|
56369
|
+
function toRow$1(id, data) {
|
|
56370
|
+
return {
|
|
56371
|
+
id,
|
|
56372
|
+
deviceId: num$1(data["deviceId"]),
|
|
56373
|
+
openedAt: num$1(data["openedAt"]),
|
|
56374
|
+
lastActiveAt: num$1(data["lastActiveAt"]),
|
|
56375
|
+
sealedAt: typeof data["sealedAt"] === "number" ? data["sealedAt"] : null,
|
|
56376
|
+
memberCount: num$1(data["memberCount"])
|
|
56377
|
+
};
|
|
56378
|
+
}
|
|
56379
|
+
function isUniqueViolation(err) {
|
|
56380
|
+
return err instanceof Error && /UNIQUE constraint failed/i.test(err.message);
|
|
56381
|
+
}
|
|
56382
|
+
var SummaryStore = class {
|
|
56383
|
+
store;
|
|
56384
|
+
logger;
|
|
56385
|
+
onEvict;
|
|
56386
|
+
constructor(deps) {
|
|
56387
|
+
this.store = deps.store;
|
|
56388
|
+
this.logger = deps.logger;
|
|
56389
|
+
this.onEvict = deps.onEvict;
|
|
56390
|
+
}
|
|
56391
|
+
static async declare(store) {
|
|
56392
|
+
await Promise.all([store.declareCollection.mutate({
|
|
56393
|
+
collection: SUMMARIES_COLLECTION,
|
|
56394
|
+
columns: [...SUMMARIES_COLUMNS],
|
|
56395
|
+
indexes: [...SUMMARIES_INDEXES]
|
|
56396
|
+
}), store.declareCollection.mutate({
|
|
56397
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
56398
|
+
columns: [...SUMMARY_MEMBERS_COLUMNS],
|
|
56399
|
+
indexes: [...SUMMARY_MEMBERS_INDEXES]
|
|
56400
|
+
})]);
|
|
56401
|
+
}
|
|
56402
|
+
/** The camera's OPEN summary, from the row — `null` when none. */
|
|
56403
|
+
async findOpen(deviceId) {
|
|
56404
|
+
const first = (await this.store.query.query({
|
|
56405
|
+
collection: SUMMARIES_COLLECTION,
|
|
56406
|
+
filter: {
|
|
56407
|
+
where: { openDeviceId: deviceId },
|
|
56408
|
+
limit: 1
|
|
56409
|
+
}
|
|
56410
|
+
}))[0];
|
|
56411
|
+
return first === void 0 ? null : toRow$1(first.id, first.data);
|
|
56412
|
+
}
|
|
56413
|
+
async listOpen() {
|
|
56414
|
+
return (await this.store.query.query({
|
|
56415
|
+
collection: SUMMARIES_COLLECTION,
|
|
56416
|
+
filter: {
|
|
56417
|
+
where: { sealedAt: null },
|
|
56418
|
+
limit: 1e4
|
|
56419
|
+
}
|
|
56420
|
+
})).filter((r) => r.data["openDeviceId"] !== null).map((r) => toRow$1(r.id, r.data));
|
|
56421
|
+
}
|
|
56422
|
+
async membersOf(summaryIdValue) {
|
|
56423
|
+
return (await this.store.query.query({
|
|
56424
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
56425
|
+
filter: {
|
|
56426
|
+
where: { summaryId: summaryIdValue },
|
|
56427
|
+
limit: 1e4
|
|
56428
|
+
}
|
|
56429
|
+
})).map((r) => ({
|
|
56430
|
+
summaryId: summaryIdValue,
|
|
56431
|
+
trackId: String(r.data["trackId"] ?? ""),
|
|
56432
|
+
deviceId: num$1(r.data["deviceId"]),
|
|
56433
|
+
joinedAt: num$1(r.data["timestamp"])
|
|
56434
|
+
}));
|
|
56435
|
+
}
|
|
56436
|
+
/**
|
|
56437
|
+
* Put a track into the camera's open summary, opening one when there is
|
|
56438
|
+
* none. Racing opens are settled by the store: the loser's insert hits the
|
|
56439
|
+
* unique `openDeviceId` and is re-read as an absorb.
|
|
56440
|
+
*/
|
|
56441
|
+
async openOrAbsorb(input) {
|
|
56442
|
+
const existing = await this.findOpen(input.deviceId);
|
|
56443
|
+
if (existing !== null) return this.absorb(existing, input);
|
|
56444
|
+
const id = summaryId(input.deviceId, input.at);
|
|
56445
|
+
try {
|
|
56446
|
+
await this.store.set.mutate({
|
|
56447
|
+
collection: SUMMARIES_COLLECTION,
|
|
56448
|
+
key: id,
|
|
56449
|
+
value: {
|
|
56450
|
+
deviceId: input.deviceId,
|
|
56451
|
+
openedAt: input.at,
|
|
56452
|
+
lastActiveAt: input.at,
|
|
56453
|
+
sealedAt: null,
|
|
56454
|
+
openDeviceId: input.deviceId,
|
|
56455
|
+
memberCount: 1
|
|
56456
|
+
}
|
|
56457
|
+
});
|
|
56458
|
+
} catch (err) {
|
|
56459
|
+
if (!isUniqueViolation(err)) throw err;
|
|
56460
|
+
const winner = await this.findOpen(input.deviceId);
|
|
56461
|
+
if (winner === null) throw err;
|
|
56462
|
+
return this.absorb(winner, input);
|
|
56463
|
+
}
|
|
56464
|
+
await this.addMember(id, input);
|
|
56465
|
+
return {
|
|
56466
|
+
id,
|
|
56467
|
+
deviceId: input.deviceId,
|
|
56468
|
+
openedAt: input.at,
|
|
56469
|
+
lastActiveAt: input.at,
|
|
56470
|
+
sealedAt: null,
|
|
56471
|
+
memberCount: 1
|
|
56472
|
+
};
|
|
56473
|
+
}
|
|
56474
|
+
async absorb(summary, input) {
|
|
56475
|
+
const members = await this.membersOf(summary.id);
|
|
56476
|
+
if (members.some((m) => m.trackId === input.trackId)) return summary;
|
|
56477
|
+
await this.addMember(summary.id, input);
|
|
56478
|
+
const memberCount = members.length + 1;
|
|
56479
|
+
const lastActiveAt = Math.max(summary.lastActiveAt, input.at);
|
|
56480
|
+
await this.store.updateWhere.mutate({
|
|
56481
|
+
collection: SUMMARIES_COLLECTION,
|
|
56482
|
+
filter: { where: {
|
|
56483
|
+
openDeviceId: summary.deviceId,
|
|
56484
|
+
openedAt: summary.openedAt
|
|
56485
|
+
} },
|
|
56486
|
+
data: {
|
|
56487
|
+
memberCount,
|
|
56488
|
+
lastActiveAt
|
|
56489
|
+
}
|
|
56490
|
+
});
|
|
56491
|
+
return {
|
|
56492
|
+
...summary,
|
|
56493
|
+
memberCount,
|
|
56494
|
+
lastActiveAt
|
|
56495
|
+
};
|
|
56496
|
+
}
|
|
56497
|
+
async addMember(summaryIdValue, input) {
|
|
56498
|
+
await this.store.insertMany.mutate({
|
|
56499
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
56500
|
+
records: [{
|
|
56501
|
+
id: memberRowId(summaryIdValue, input.trackId),
|
|
56502
|
+
data: {
|
|
56503
|
+
summaryId: summaryIdValue,
|
|
56504
|
+
trackId: input.trackId,
|
|
56505
|
+
deviceId: input.deviceId,
|
|
56506
|
+
timestamp: input.at
|
|
56507
|
+
}
|
|
56508
|
+
}]
|
|
56509
|
+
});
|
|
56510
|
+
}
|
|
56511
|
+
/**
|
|
56512
|
+
* Seal in place. `true` when THIS call sealed it; `false` when the row was
|
|
56513
|
+
* already sealed (or gone) — the second sealer learns, it does not write.
|
|
56514
|
+
*/
|
|
56515
|
+
async seal(summary, sealedAt) {
|
|
56516
|
+
const { updated } = await this.store.updateWhere.mutate({
|
|
56517
|
+
collection: SUMMARIES_COLLECTION,
|
|
56518
|
+
filter: { where: {
|
|
56519
|
+
openDeviceId: summary.deviceId,
|
|
56520
|
+
openedAt: summary.openedAt
|
|
56521
|
+
} },
|
|
56522
|
+
data: {
|
|
56523
|
+
sealedAt,
|
|
56524
|
+
openDeviceId: null
|
|
56525
|
+
}
|
|
56526
|
+
});
|
|
56527
|
+
if (updated > 1) this.logger.error("summary seal touched more than one row — the open key is not unique", {
|
|
56528
|
+
tags: { deviceId: summary.deviceId },
|
|
56529
|
+
meta: {
|
|
56530
|
+
summaryId: summary.id,
|
|
56531
|
+
updated
|
|
56532
|
+
}
|
|
56533
|
+
});
|
|
56534
|
+
return updated >= 1;
|
|
56535
|
+
}
|
|
56536
|
+
/**
|
|
56537
|
+
* Track cascade leaf: a member row whose track is gone is gone too, and
|
|
56538
|
+
* the summary's count follows. The envelope itself outlives its members
|
|
56539
|
+
* — it is an age-keyed root (`RETENTION_ROOTS`, `summary`).
|
|
56540
|
+
*/
|
|
56541
|
+
async deleteByTracks(trackIds) {
|
|
56542
|
+
if (trackIds.length === 0) return [{
|
|
56543
|
+
family: "summaries",
|
|
56544
|
+
rows: 0,
|
|
56545
|
+
bytes: 0
|
|
56546
|
+
}];
|
|
56547
|
+
const members = await this.store.query.query({
|
|
56548
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
56549
|
+
filter: {
|
|
56550
|
+
whereIn: { trackId: [...trackIds] },
|
|
56551
|
+
limit: 1e4
|
|
56552
|
+
}
|
|
56553
|
+
});
|
|
56554
|
+
if (members.length === 0) return [{
|
|
56555
|
+
family: "summaries",
|
|
56556
|
+
rows: 0,
|
|
56557
|
+
bytes: 0
|
|
56558
|
+
}];
|
|
56559
|
+
const { deleted } = await this.store.deleteWhere.mutate({
|
|
56560
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
56561
|
+
filter: { whereIn: { trackId: [...trackIds] } }
|
|
56562
|
+
});
|
|
56563
|
+
const touched = new Set(members.map((m) => String(m.data["summaryId"] ?? "")));
|
|
56564
|
+
for (const summaryIdValue of touched) {
|
|
56565
|
+
const remaining = await this.membersOf(summaryIdValue);
|
|
56566
|
+
const raw = await this.store.get.query({
|
|
56567
|
+
collection: SUMMARIES_COLLECTION,
|
|
56568
|
+
key: summaryIdValue
|
|
56569
|
+
});
|
|
56570
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
56571
|
+
const row = raw;
|
|
56572
|
+
await this.store.updateWhere.mutate({
|
|
56573
|
+
collection: SUMMARIES_COLLECTION,
|
|
56574
|
+
filter: { where: {
|
|
56575
|
+
deviceId: num$1(row["deviceId"]),
|
|
56576
|
+
openedAt: num$1(row["openedAt"])
|
|
56577
|
+
} },
|
|
56578
|
+
data: { memberCount: remaining.length }
|
|
56579
|
+
});
|
|
56580
|
+
}
|
|
56581
|
+
return [{
|
|
56582
|
+
family: "summaries",
|
|
56583
|
+
rows: deleted,
|
|
56584
|
+
bytes: 0
|
|
56585
|
+
}];
|
|
56586
|
+
}
|
|
56587
|
+
/** The subset of `ids` that still have a row — the orphan audit's probe. Throws on a failed read. */
|
|
56588
|
+
async existingIds(ids) {
|
|
56589
|
+
const found = /* @__PURE__ */ new Set();
|
|
56590
|
+
await Promise.all(ids.map(async (id) => {
|
|
56591
|
+
const raw = await this.store.get.query({
|
|
56592
|
+
collection: SUMMARIES_COLLECTION,
|
|
56593
|
+
key: id
|
|
56594
|
+
});
|
|
56595
|
+
if (typeof raw === "object" && raw !== null) found.add(id);
|
|
56596
|
+
}));
|
|
56597
|
+
return found;
|
|
56598
|
+
}
|
|
56599
|
+
/** Age sweep on the device window: everything opened before `cutoffMs`, members included. */
|
|
56600
|
+
async evictBefore(deviceId, cutoffMs) {
|
|
56601
|
+
const rows = await this.store.query.query({
|
|
56602
|
+
collection: SUMMARIES_COLLECTION,
|
|
56603
|
+
filter: {
|
|
56604
|
+
where: { deviceId },
|
|
56605
|
+
whereBetween: { openedAt: [0, cutoffMs - 1] },
|
|
56606
|
+
limit: 1e4
|
|
56607
|
+
}
|
|
56608
|
+
});
|
|
56609
|
+
if (rows.length === 0) return 0;
|
|
56610
|
+
const ids = rows.map((r) => r.id);
|
|
56611
|
+
await this.onEvict?.(ids);
|
|
56612
|
+
await this.store.deleteWhere.mutate({
|
|
56613
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
56614
|
+
filter: { whereIn: { summaryId: ids } }
|
|
56615
|
+
});
|
|
56616
|
+
const { deleted } = await this.store.deleteWhere.mutate({
|
|
56617
|
+
collection: SUMMARIES_COLLECTION,
|
|
56618
|
+
filter: {
|
|
56619
|
+
where: { deviceId },
|
|
56620
|
+
whereBetween: { openedAt: [0, cutoffMs - 1] }
|
|
56621
|
+
}
|
|
56622
|
+
});
|
|
56623
|
+
return deleted;
|
|
56624
|
+
}
|
|
56625
|
+
};
|
|
56243
56626
|
/**
|
|
56244
56627
|
* Where the completion marker lives.
|
|
56245
56628
|
*
|
|
@@ -58535,6 +58918,10 @@ var ANALYTICS_COLLECTIONS = [
|
|
|
58535
58918
|
collection: PLATES_COLLECTION,
|
|
58536
58919
|
classification: derived(PLATE_COLUMNS)
|
|
58537
58920
|
},
|
|
58921
|
+
{
|
|
58922
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
58923
|
+
classification: derived(SUMMARY_MEMBERS_COLUMNS)
|
|
58924
|
+
},
|
|
58538
58925
|
{
|
|
58539
58926
|
collection: MEDIA_COLLECTION,
|
|
58540
58927
|
classification: {
|
|
@@ -58578,6 +58965,13 @@ var ANALYTICS_COLLECTIONS = [
|
|
|
58578
58965
|
rootId: "ops-log"
|
|
58579
58966
|
}
|
|
58580
58967
|
},
|
|
58968
|
+
{
|
|
58969
|
+
collection: SUMMARIES_COLLECTION,
|
|
58970
|
+
classification: {
|
|
58971
|
+
kind: "retention-root",
|
|
58972
|
+
rootId: "summary"
|
|
58973
|
+
}
|
|
58974
|
+
},
|
|
58581
58975
|
{
|
|
58582
58976
|
collection: IDENTITIES_COLLECTION,
|
|
58583
58977
|
classification: {
|
|
@@ -58737,6 +59131,10 @@ function resolveMediaOwner(row) {
|
|
|
58737
59131
|
kind: "event",
|
|
58738
59132
|
eventId: ownerId
|
|
58739
59133
|
};
|
|
59134
|
+
case "summary": return {
|
|
59135
|
+
kind: "summary",
|
|
59136
|
+
summaryId: ownerId
|
|
59137
|
+
};
|
|
58740
59138
|
case "face": return ownerId.startsWith("face-") ? {
|
|
58741
59139
|
kind: "track",
|
|
58742
59140
|
trackId: ownerId.slice(FACE_MEDIA_OWNER_PREFIX.length)
|
|
@@ -58823,11 +59221,14 @@ var DEFAULT_ORPHAN_LIMITS = {
|
|
|
58823
59221
|
function questionsFor(refs) {
|
|
58824
59222
|
const trackIds = /* @__PURE__ */ new Set();
|
|
58825
59223
|
const eventIds = /* @__PURE__ */ new Set();
|
|
59224
|
+
const summaryIds = /* @__PURE__ */ new Set();
|
|
58826
59225
|
for (const ref of refs) if (ref.kind === "track") trackIds.add(ref.trackId);
|
|
58827
59226
|
else if (ref.kind === "event") eventIds.add(ref.eventId);
|
|
59227
|
+
else if (ref.kind === "summary") summaryIds.add(ref.summaryId);
|
|
58828
59228
|
return {
|
|
58829
59229
|
trackIds: [...trackIds],
|
|
58830
|
-
eventIds: [...eventIds]
|
|
59230
|
+
eventIds: [...eventIds],
|
|
59231
|
+
summaryIds: [...summaryIds]
|
|
58831
59232
|
};
|
|
58832
59233
|
}
|
|
58833
59234
|
/**
|
|
@@ -58859,12 +59260,14 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
58859
59260
|
break;
|
|
58860
59261
|
}
|
|
58861
59262
|
const refs = rows.map((row) => scope.resolve(row));
|
|
58862
|
-
const { trackIds, eventIds } = questionsFor(refs);
|
|
59263
|
+
const { trackIds, eventIds, summaryIds } = questionsFor(refs);
|
|
58863
59264
|
let liveTracks;
|
|
58864
59265
|
let liveEvents;
|
|
59266
|
+
let liveSummaries;
|
|
58865
59267
|
try {
|
|
58866
59268
|
liveTracks = trackIds.length > 0 ? await deps.liveTrackIds(trackIds) : /* @__PURE__ */ new Set();
|
|
58867
59269
|
liveEvents = eventIds.length > 0 ? await deps.liveEventIds(eventIds) : /* @__PURE__ */ new Set();
|
|
59270
|
+
liveSummaries = summaryIds.length > 0 ? await deps.liveSummaryIds(summaryIds) : /* @__PURE__ */ new Set();
|
|
58868
59271
|
} catch (err) {
|
|
58869
59272
|
deps.logger.warn("orphan audit: liveness probe failed — scope abandoned for this run", { meta: {
|
|
58870
59273
|
collection: scope.collection,
|
|
@@ -58888,7 +59291,7 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
58888
59291
|
tally.unresolvable += 1;
|
|
58889
59292
|
continue;
|
|
58890
59293
|
}
|
|
58891
|
-
if (ref.kind === "track" ? liveTracks.has(ref.trackId) : ref.kind === "event" ? liveEvents.has(ref.eventId) : false) continue;
|
|
59294
|
+
if (ref.kind === "track" ? liveTracks.has(ref.trackId) : ref.kind === "event" ? liveEvents.has(ref.eventId) : ref.kind === "summary" ? liveSummaries.has(ref.summaryId) : false) continue;
|
|
58892
59295
|
const ts = num(row.data, scope.timestampField);
|
|
58893
59296
|
if (ts !== null && ts > graceFloor) {
|
|
58894
59297
|
tally.withinGrace += 1;
|
|
@@ -59138,12 +59541,14 @@ async function sweepTracklessRoots(deps) {
|
|
|
59138
59541
|
cutoffMs
|
|
59139
59542
|
});
|
|
59140
59543
|
const sensor = deps.evictSensorEvents ? await deps.evictSensorEvents(deviceId, cutoffMs) : 0;
|
|
59544
|
+
const summaries = deps.evictSummaries ? await deps.evictSummaries(deviceId, cutoffMs) : 0;
|
|
59141
59545
|
const evicted = {
|
|
59142
59546
|
motion: events.motion,
|
|
59143
59547
|
audio: events.audio,
|
|
59144
|
-
sensor
|
|
59548
|
+
sensor,
|
|
59549
|
+
summaries
|
|
59145
59550
|
};
|
|
59146
|
-
if (evicted.motion.length + evicted.audio.length + evicted.sensor > 0) await deps.onEvicted(deviceId, evicted, cutoffs);
|
|
59551
|
+
if (evicted.motion.length + evicted.audio.length + evicted.sensor + evicted.summaries > 0) await deps.onEvicted(deviceId, evicted, cutoffs);
|
|
59147
59552
|
} catch (err) {
|
|
59148
59553
|
deps.onError(deviceId, err);
|
|
59149
59554
|
}
|
|
@@ -60103,7 +60508,7 @@ function createNativeFrameTransport(deps) {
|
|
|
60103
60508
|
} catch (err) {
|
|
60104
60509
|
logger.debug("full-frame jpeg fetch failed", { meta: {
|
|
60105
60510
|
nodeId: handle.nodeId,
|
|
60106
|
-
error: errMsg(err)
|
|
60511
|
+
error: errMsg$1(err)
|
|
60107
60512
|
} });
|
|
60108
60513
|
return null;
|
|
60109
60514
|
}
|
|
@@ -60152,7 +60557,7 @@ function createNativeFrameTransport(deps) {
|
|
|
60152
60557
|
try {
|
|
60153
60558
|
return await requestNativeCrop(frameHandle, bbox, maxWidth, normalize);
|
|
60154
60559
|
} catch (err) {
|
|
60155
|
-
cropMetricLogger.debug("native crop request failed — falling back", { meta: { error: errMsg(err) } });
|
|
60560
|
+
cropMetricLogger.debug("native crop request failed — falling back", { meta: { error: errMsg$1(err) } });
|
|
60156
60561
|
return null;
|
|
60157
60562
|
}
|
|
60158
60563
|
};
|
|
@@ -60201,7 +60606,7 @@ function createNativeFrameTransport(deps) {
|
|
|
60201
60606
|
const tiered = await fetchNativeFullFrameTiered(handle, DISPLAY_FALLBACK_MAX_WIDTH).catch((err) => {
|
|
60202
60607
|
logger.child("DisplayCrop").debug("fallback full-frame fetch failed — treating as a miss", { meta: {
|
|
60203
60608
|
nodeId: handle.nodeId,
|
|
60204
|
-
error: errMsg(err)
|
|
60609
|
+
error: errMsg$1(err)
|
|
60205
60610
|
} });
|
|
60206
60611
|
return null;
|
|
60207
60612
|
});
|
|
@@ -64838,6 +65243,310 @@ function rowFromMetadata(id, metadata) {
|
|
|
64838
65243
|
};
|
|
64839
65244
|
}
|
|
64840
65245
|
//#endregion
|
|
65246
|
+
//#region src/pipeline-analytics/summary/summary-score.ts
|
|
65247
|
+
/** A member whose own box is covered this much by others is hidden. */
|
|
65248
|
+
var OCCLUDED_COVER_FRACTION = .5;
|
|
65249
|
+
/** A box covering this share of the frame scores full marks on area. */
|
|
65250
|
+
var FULL_AREA_SHARE = .25;
|
|
65251
|
+
var WEIGHT_CONFIDENCE = .3;
|
|
65252
|
+
var WEIGHT_AREA = .25;
|
|
65253
|
+
var WEIGHT_RECOGNITION = .25;
|
|
65254
|
+
var WEIGHT_PLACEMENT = .2;
|
|
65255
|
+
function intersectionArea(a, b) {
|
|
65256
|
+
const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
|
|
65257
|
+
const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
|
|
65258
|
+
return w > 0 && h > 0 ? w * h : 0;
|
|
65259
|
+
}
|
|
65260
|
+
/**
|
|
65261
|
+
* Share of `own` covered by `others`, 0..1. Pairwise intersections summed and
|
|
65262
|
+
* capped at the own area: when two others overlap each other on top of this
|
|
65263
|
+
* box the sum over-counts, which only ever calls a member MORE hidden, never
|
|
65264
|
+
* less — the safe side for a shot meant to show everyone.
|
|
65265
|
+
*/
|
|
65266
|
+
function coveredFraction(own, others) {
|
|
65267
|
+
const area = own.width * own.height;
|
|
65268
|
+
if (area <= 0) return 1;
|
|
65269
|
+
const covered = others.reduce((sum, other) => sum + intersectionArea(own, other), 0);
|
|
65270
|
+
return Math.min(1, covered / area);
|
|
65271
|
+
}
|
|
65272
|
+
function clamp01(value) {
|
|
65273
|
+
return Math.max(0, Math.min(1, value));
|
|
65274
|
+
}
|
|
65275
|
+
function placement(box, frameWidth, frameHeight) {
|
|
65276
|
+
const cx = box.x + box.width / 2;
|
|
65277
|
+
const cy = box.y + box.height / 2;
|
|
65278
|
+
const dx = cx - frameWidth / 2;
|
|
65279
|
+
const dy = cy - frameHeight / 2;
|
|
65280
|
+
const halfDiagonal = Math.hypot(frameWidth, frameHeight) / 2;
|
|
65281
|
+
return halfDiagonal <= 0 ? 0 : 1 - clamp01(Math.hypot(dx, dy) / halfDiagonal);
|
|
65282
|
+
}
|
|
65283
|
+
function memberQuality(d, frameWidth, frameHeight) {
|
|
65284
|
+
const frameArea = frameWidth * frameHeight;
|
|
65285
|
+
const cappedArea = clamp01((frameArea <= 0 ? 0 : d.bbox.width * d.bbox.height / frameArea) / FULL_AREA_SHARE);
|
|
65286
|
+
return WEIGHT_CONFIDENCE * clamp01(d.confidence) + WEIGHT_AREA * cappedArea + WEIGHT_RECOGNITION * (d.recognized ? 1 : 0) + WEIGHT_PLACEMENT * placement(d.bbox, frameWidth, frameHeight);
|
|
65287
|
+
}
|
|
65288
|
+
/** Score one frame for one summary. Pure; never throws on empty input. */
|
|
65289
|
+
function scoreSummaryFrame(input) {
|
|
65290
|
+
const present = input.detections.filter((d) => input.members.has(d.trackId));
|
|
65291
|
+
const visible = present.filter((d) => coveredFraction(d.bbox, present.filter((o) => o !== d).map((o) => o.bbox)) < OCCLUDED_COVER_FRACTION);
|
|
65292
|
+
if (visible.length === 0) return {
|
|
65293
|
+
visible: 0,
|
|
65294
|
+
quality: 0
|
|
65295
|
+
};
|
|
65296
|
+
const quality = visible.reduce((sum, d) => sum + memberQuality(d, input.frameWidth, input.frameHeight), 0) / visible.length;
|
|
65297
|
+
return {
|
|
65298
|
+
visible: visible.length,
|
|
65299
|
+
quality
|
|
65300
|
+
};
|
|
65301
|
+
}
|
|
65302
|
+
/**
|
|
65303
|
+
* Lexicographic: more visible members wins outright; only equal counts fall
|
|
65304
|
+
* through to quality. Positive when `a` is the better shot.
|
|
65305
|
+
*/
|
|
65306
|
+
function compareSummaryScores(a, b) {
|
|
65307
|
+
if (a.visible !== b.visible) return a.visible - b.visible;
|
|
65308
|
+
return a.quality - b.quality;
|
|
65309
|
+
}
|
|
65310
|
+
/** A candidate replaces the best only when it is STRICTLY better — a tie keeps
|
|
65311
|
+
* the pixels already materialised (they cost a native round trip). */
|
|
65312
|
+
function isBetterSummaryScore(candidate, best) {
|
|
65313
|
+
if (candidate.visible === 0) return false;
|
|
65314
|
+
return best === null || compareSummaryScores(candidate, best) > 0;
|
|
65315
|
+
}
|
|
65316
|
+
//#endregion
|
|
65317
|
+
//#region src/pipeline-analytics/summary/group-shot.ts
|
|
65318
|
+
/**
|
|
65319
|
+
* The running argmax of one open summary's group shot.
|
|
65320
|
+
*
|
|
65321
|
+
* RAM only, on purpose: it decides WHEN pixels are worth a native round trip
|
|
65322
|
+
* (only when the maximum improves — D9/D18), it is not the record of the
|
|
65323
|
+
* shot. The record is the media row the caller writes under the summary's
|
|
65324
|
+
* id; after a restart an open summary simply starts its argmax again and
|
|
65325
|
+
* replaces the row the next time it beats what it can remember, which is
|
|
65326
|
+
* nothing. That is a weaker shot for one session, never a wrong one.
|
|
65327
|
+
*/
|
|
65328
|
+
var GroupShotTracker = class {
|
|
65329
|
+
best = /* @__PURE__ */ new Map();
|
|
65330
|
+
/**
|
|
65331
|
+
* Offer a frame. Returns `true` when it beats the summary's best so far —
|
|
65332
|
+
* the caller's cue to materialise the pixels for THIS frame, now, while its
|
|
65333
|
+
* handle is still leased.
|
|
65334
|
+
*/
|
|
65335
|
+
offer(summaryId, score, frame) {
|
|
65336
|
+
if (!isBetterSummaryScore(score, (this.best.get(summaryId) ?? null)?.score ?? null)) return false;
|
|
65337
|
+
this.best.set(summaryId, {
|
|
65338
|
+
score,
|
|
65339
|
+
frame
|
|
65340
|
+
});
|
|
65341
|
+
return true;
|
|
65342
|
+
}
|
|
65343
|
+
bestOf(summaryId) {
|
|
65344
|
+
return this.best.get(summaryId) ?? null;
|
|
65345
|
+
}
|
|
65346
|
+
/** A sealed or evicted summary takes no more frames; drop its state. */
|
|
65347
|
+
forget(summaryId) {
|
|
65348
|
+
this.best.delete(summaryId);
|
|
65349
|
+
}
|
|
65350
|
+
get size() {
|
|
65351
|
+
return this.best.size;
|
|
65352
|
+
}
|
|
65353
|
+
};
|
|
65354
|
+
//#endregion
|
|
65355
|
+
//#region src/pipeline-analytics/summary/group-shot-materialiser.ts
|
|
65356
|
+
async function persistSummaryGroupShot(deps, input) {
|
|
65357
|
+
const { deviceId, summaryId, frameHandle } = input;
|
|
65358
|
+
const drop = {
|
|
65359
|
+
tags: { deviceId },
|
|
65360
|
+
meta: {
|
|
65361
|
+
summaryId,
|
|
65362
|
+
nodeId: frameHandle.nodeId
|
|
65363
|
+
}
|
|
65364
|
+
};
|
|
65365
|
+
try {
|
|
65366
|
+
const result = await deps.fetchNativeFullFrame(frameHandle, input.maxWidth);
|
|
65367
|
+
const native = result?.frame;
|
|
65368
|
+
if (!native || native.format !== "rgb" || native.width <= 0 || native.height <= 0) {
|
|
65369
|
+
deps.logger.warn("group shot native miss — the previous shot stands", {
|
|
65370
|
+
...drop,
|
|
65371
|
+
meta: {
|
|
65372
|
+
...drop.meta,
|
|
65373
|
+
shmId: frameHandle.shmId
|
|
65374
|
+
}
|
|
65375
|
+
});
|
|
65376
|
+
return { kind: "native-miss" };
|
|
65377
|
+
}
|
|
65378
|
+
if (!keyFrameAcceptsTier(result.tier)) {
|
|
65379
|
+
deps.logger.warn("group shot RAM tier rejected — the previous shot stands", {
|
|
65380
|
+
...drop,
|
|
65381
|
+
meta: {
|
|
65382
|
+
...drop.meta,
|
|
65383
|
+
shmId: frameHandle.shmId,
|
|
65384
|
+
tier: result.tier
|
|
65385
|
+
}
|
|
65386
|
+
});
|
|
65387
|
+
return { kind: "ram-tier" };
|
|
65388
|
+
}
|
|
65389
|
+
const { keyFrame, keyFrameSmall } = await deps.encodeVariants(native);
|
|
65390
|
+
const keyFrameKey = await deps.media.putReplacing({
|
|
65391
|
+
deviceId,
|
|
65392
|
+
ownerKind: "summary",
|
|
65393
|
+
ownerId: summaryId,
|
|
65394
|
+
kind: "keyFrame",
|
|
65395
|
+
timestamp: input.timestamp,
|
|
65396
|
+
data: keyFrame
|
|
65397
|
+
});
|
|
65398
|
+
try {
|
|
65399
|
+
await deps.media.putReplacing({
|
|
65400
|
+
deviceId,
|
|
65401
|
+
ownerKind: "summary",
|
|
65402
|
+
ownerId: summaryId,
|
|
65403
|
+
kind: "keyFrameSmall",
|
|
65404
|
+
timestamp: input.timestamp,
|
|
65405
|
+
data: keyFrameSmall
|
|
65406
|
+
});
|
|
65407
|
+
} catch (err) {
|
|
65408
|
+
deps.logger.warn("group shot web companion write failed — the native shot is stored", {
|
|
65409
|
+
...drop,
|
|
65410
|
+
meta: {
|
|
65411
|
+
...drop.meta,
|
|
65412
|
+
error: errMsg$1(err)
|
|
65413
|
+
}
|
|
65414
|
+
});
|
|
65415
|
+
}
|
|
65416
|
+
return {
|
|
65417
|
+
kind: "landed",
|
|
65418
|
+
keyFrameKey
|
|
65419
|
+
};
|
|
65420
|
+
} catch (err) {
|
|
65421
|
+
const error = errMsg$1(err);
|
|
65422
|
+
deps.logger.warn("group shot capture failed", {
|
|
65423
|
+
...drop,
|
|
65424
|
+
meta: {
|
|
65425
|
+
...drop.meta,
|
|
65426
|
+
error
|
|
65427
|
+
}
|
|
65428
|
+
});
|
|
65429
|
+
return {
|
|
65430
|
+
kind: "error",
|
|
65431
|
+
error
|
|
65432
|
+
};
|
|
65433
|
+
}
|
|
65434
|
+
}
|
|
65435
|
+
//#endregion
|
|
65436
|
+
//#region src/pipeline-analytics/summary/summary-sealer.ts
|
|
65437
|
+
function errMsg(err) {
|
|
65438
|
+
return err instanceof Error ? err.message : String(err);
|
|
65439
|
+
}
|
|
65440
|
+
var SummarySealer = class {
|
|
65441
|
+
deps;
|
|
65442
|
+
sweepInFlight = false;
|
|
65443
|
+
/**
|
|
65444
|
+
* What the FRAME path may ask at frame-rate: the open summary of a camera
|
|
65445
|
+
* and the members it has seen THIS process open. Fed only by this class's
|
|
65446
|
+
* own writes, never the authority (D224): a fresh process answers `null`
|
|
65447
|
+
* until a track opens, and a summary open across a restart is scored on the
|
|
65448
|
+
* members that open after it — a weaker shot, never a wrong one.
|
|
65449
|
+
*/
|
|
65450
|
+
openMirror = /* @__PURE__ */ new Map();
|
|
65451
|
+
constructor(deps) {
|
|
65452
|
+
this.deps = deps;
|
|
65453
|
+
}
|
|
65454
|
+
openFor(deviceId) {
|
|
65455
|
+
return this.openMirror.get(deviceId) ?? null;
|
|
65456
|
+
}
|
|
65457
|
+
async onTrackOpened(input) {
|
|
65458
|
+
try {
|
|
65459
|
+
const row = await this.deps.summaries.openOrAbsorb({
|
|
65460
|
+
deviceId: input.deviceId,
|
|
65461
|
+
trackId: input.trackId,
|
|
65462
|
+
at: this.deps.now()
|
|
65463
|
+
});
|
|
65464
|
+
const mirror = this.openMirror.get(input.deviceId);
|
|
65465
|
+
if (mirror !== void 0 && mirror.id === row.id) mirror.members.add(input.trackId);
|
|
65466
|
+
else this.openMirror.set(input.deviceId, {
|
|
65467
|
+
id: row.id,
|
|
65468
|
+
members: new Set([input.trackId])
|
|
65469
|
+
});
|
|
65470
|
+
} catch (err) {
|
|
65471
|
+
this.deps.logger.warn("summary: could not open or absorb for a track", {
|
|
65472
|
+
tags: { deviceId: input.deviceId },
|
|
65473
|
+
meta: {
|
|
65474
|
+
trackId: input.trackId,
|
|
65475
|
+
error: errMsg(err)
|
|
65476
|
+
}
|
|
65477
|
+
});
|
|
65478
|
+
}
|
|
65479
|
+
}
|
|
65480
|
+
/** One sweep at a time (THE latch). A tick that finds one in flight returns. */
|
|
65481
|
+
async sweep() {
|
|
65482
|
+
if (this.sweepInFlight) return;
|
|
65483
|
+
this.sweepInFlight = true;
|
|
65484
|
+
try {
|
|
65485
|
+
const open = await this.deps.summaries.listOpen();
|
|
65486
|
+
for (const summary of open) await this.sealIfDone(summary);
|
|
65487
|
+
} catch (err) {
|
|
65488
|
+
this.deps.logger.warn("summary sweep: could not list open summaries", { meta: { error: errMsg(err) } });
|
|
65489
|
+
} finally {
|
|
65490
|
+
this.sweepInFlight = false;
|
|
65491
|
+
}
|
|
65492
|
+
}
|
|
65493
|
+
async sealIfDone(summary) {
|
|
65494
|
+
const { id: summaryId, deviceId } = summary;
|
|
65495
|
+
let openTracks;
|
|
65496
|
+
try {
|
|
65497
|
+
openTracks = await this.deps.openTrackIds(deviceId);
|
|
65498
|
+
} catch (err) {
|
|
65499
|
+
this.deps.logger.warn("summary sweep: could not read open tracks — seal aborted this sweep", {
|
|
65500
|
+
tags: { deviceId },
|
|
65501
|
+
meta: {
|
|
65502
|
+
summaryId,
|
|
65503
|
+
error: errMsg(err)
|
|
65504
|
+
}
|
|
65505
|
+
});
|
|
65506
|
+
return;
|
|
65507
|
+
}
|
|
65508
|
+
if (openTracks.length > 0) return;
|
|
65509
|
+
let quiet;
|
|
65510
|
+
try {
|
|
65511
|
+
quiet = await this.deps.isCameraQuiet(deviceId);
|
|
65512
|
+
} catch (err) {
|
|
65513
|
+
this.deps.logger.warn("summary sweep: could not read the stationary registry — seal aborted this sweep", {
|
|
65514
|
+
tags: { deviceId },
|
|
65515
|
+
meta: {
|
|
65516
|
+
summaryId,
|
|
65517
|
+
error: errMsg(err)
|
|
65518
|
+
}
|
|
65519
|
+
});
|
|
65520
|
+
return;
|
|
65521
|
+
}
|
|
65522
|
+
if (!quiet) return;
|
|
65523
|
+
const sealedAt = this.deps.now();
|
|
65524
|
+
try {
|
|
65525
|
+
if (await this.deps.summaries.seal(summary, sealedAt)) {
|
|
65526
|
+
const mirror = this.openMirror.get(deviceId);
|
|
65527
|
+
const members = mirror?.id === summaryId ? mirror.members : /* @__PURE__ */ new Set();
|
|
65528
|
+
if (mirror?.id === summaryId) this.openMirror.delete(deviceId);
|
|
65529
|
+
this.deps.onSealed?.(summaryId, members);
|
|
65530
|
+
this.deps.logger.info("summary sealed", {
|
|
65531
|
+
tags: { deviceId },
|
|
65532
|
+
meta: {
|
|
65533
|
+
summaryId,
|
|
65534
|
+
sealedAt
|
|
65535
|
+
}
|
|
65536
|
+
});
|
|
65537
|
+
}
|
|
65538
|
+
} catch (err) {
|
|
65539
|
+
this.deps.logger.warn("summary sweep: seal failed — the row stays open for the next sweep", {
|
|
65540
|
+
tags: { deviceId },
|
|
65541
|
+
meta: {
|
|
65542
|
+
summaryId,
|
|
65543
|
+
error: errMsg(err)
|
|
65544
|
+
}
|
|
65545
|
+
});
|
|
65546
|
+
}
|
|
65547
|
+
}
|
|
65548
|
+
};
|
|
65549
|
+
//#endregion
|
|
64841
65550
|
//#region src/pipeline-analytics/store/settings-raw-mirror.ts
|
|
64842
65551
|
/** How long a mirrored entry is served before a refresh is started behind the
|
|
64843
65552
|
* next reader. See the module doc for why this matches the per-kind caches'
|
|
@@ -66150,6 +66859,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
66150
66859
|
storageMigrationLeaseId = null;
|
|
66151
66860
|
/** Per-camera history of LINKED-device sensor state changes (Part B). */
|
|
66152
66861
|
sensorEventStore = null;
|
|
66862
|
+
/** The per-camera session envelope (D359). Bound with the other stores at boot. */
|
|
66863
|
+
summaryStore = null;
|
|
66864
|
+
/** The ONLY writer of a summary's lifecycle (D359). */
|
|
66865
|
+
summarySealer = null;
|
|
66866
|
+
/** The running argmax of every open summary's group shot (D359 phase 3). RAM only. */
|
|
66867
|
+
groupShots = new GroupShotTracker();
|
|
66868
|
+
/**
|
|
66869
|
+
* Tracks a gallery match has named, for the group-shot score's recognition
|
|
66870
|
+
* term. "Recognised so far" rather than "at that instant": the match arrives
|
|
66871
|
+
* on the detail plane, after the frame; the frames that follow carry it.
|
|
66872
|
+
* Pruned when the track's summary seals, so it is bounded by open summaries.
|
|
66873
|
+
*/
|
|
66874
|
+
recognizedTrackIds = /* @__PURE__ */ new Set();
|
|
66153
66875
|
/** Ingest-side reverse index (sensor device → linked camera ids), TTL-cached
|
|
66154
66876
|
* so the DeviceStateChanged handler stays cheap. */
|
|
66155
66877
|
linkedCamerasCache = null;
|
|
@@ -66429,7 +67151,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
66429
67151
|
};
|
|
66430
67152
|
this.ctx.logger.info("nc-action data-plane served", { meta: { served: served !== null } });
|
|
66431
67153
|
} catch (err) {
|
|
66432
|
-
this.ctx.logger.warn("nc-action data-plane failed to serve", { meta: { error: errMsg(err) } });
|
|
67154
|
+
this.ctx.logger.warn("nc-action data-plane failed to serve", { meta: { error: errMsg$1(err) } });
|
|
66433
67155
|
}
|
|
66434
67156
|
}
|
|
66435
67157
|
/** Mints a button's callback URL once the action plane is served; null until
|
|
@@ -66463,7 +67185,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
66463
67185
|
await store.sweep();
|
|
66464
67186
|
await index.prune(this.isPostProcessingNode ? await store.keptIds() : null);
|
|
66465
67187
|
} catch (err) {
|
|
66466
|
-
this.ctx.logger.warn("nc artifact prune failed", { meta: { error: errMsg(err) } });
|
|
67188
|
+
this.ctx.logger.warn("nc artifact prune failed", { meta: { error: errMsg$1(err) } });
|
|
66467
67189
|
}
|
|
66468
67190
|
};
|
|
66469
67191
|
run();
|
|
@@ -66523,7 +67245,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
66523
67245
|
retentionDays: Math.round(store.retentionMs / 864e5)
|
|
66524
67246
|
} });
|
|
66525
67247
|
} catch (err) {
|
|
66526
|
-
this.ctx.logger.warn("nc-artifact data-plane failed to serve", { meta: { error: errMsg(err) } });
|
|
67248
|
+
this.ctx.logger.warn("nc-artifact data-plane failed to serve", { meta: { error: errMsg$1(err) } });
|
|
66527
67249
|
}
|
|
66528
67250
|
}
|
|
66529
67251
|
/** Public base URL for event thumbnails: `/addon/<addonId>/event-media`.
|
|
@@ -66767,7 +67489,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
66767
67489
|
tags: { deviceId },
|
|
66768
67490
|
meta: {
|
|
66769
67491
|
deviceId,
|
|
66770
|
-
error: errMsg(error)
|
|
67492
|
+
error: errMsg$1(error)
|
|
66771
67493
|
}
|
|
66772
67494
|
});
|
|
66773
67495
|
}
|
|
@@ -66796,7 +67518,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
66796
67518
|
tags: { deviceId },
|
|
66797
67519
|
meta: {
|
|
66798
67520
|
deviceId,
|
|
66799
|
-
error: errMsg(error)
|
|
67521
|
+
error: errMsg$1(error)
|
|
66800
67522
|
}
|
|
66801
67523
|
});
|
|
66802
67524
|
}
|
|
@@ -67257,7 +67979,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
67257
67979
|
*/
|
|
67258
67980
|
armAlarmPanelDeclaration(api) {
|
|
67259
67981
|
this.declareAlarmPanel(api).catch((err) => {
|
|
67260
|
-
this.ctx.logger.warn("alarm panel declaration could not be armed — the Alarm tab stays empty until this addon restarts", { meta: { error: errMsg(err) } });
|
|
67982
|
+
this.ctx.logger.warn("alarm panel declaration could not be armed — the Alarm tab stays empty until this addon restarts", { meta: { error: errMsg$1(err) } });
|
|
67261
67983
|
});
|
|
67262
67984
|
}
|
|
67263
67985
|
async declareAlarmPanel(api) {
|
|
@@ -67388,6 +68110,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
67388
68110
|
MediaStore.declare(api.settingsStore),
|
|
67389
68111
|
EventStore.declare(api.settingsStore),
|
|
67390
68112
|
SensorEventStore.declare(api.settingsStore),
|
|
68113
|
+
SummaryStore.declare(api.settingsStore),
|
|
67391
68114
|
IdentityStore.declare(api.settingsStore),
|
|
67392
68115
|
FaceStore.declare(api.settingsStore),
|
|
67393
68116
|
PlateStore.declare(api.settingsStore),
|
|
@@ -67409,7 +68132,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
67409
68132
|
logger: this.ctx.logger.child("viewer-settings")
|
|
67410
68133
|
});
|
|
67411
68134
|
} catch (err) {
|
|
67412
|
-
this.ctx.logger.warn("viewer-settings legacy adoption threw", { meta: { error: errMsg(err) } });
|
|
68135
|
+
this.ctx.logger.warn("viewer-settings legacy adoption threw", { meta: { error: errMsg$1(err) } });
|
|
67413
68136
|
}
|
|
67414
68137
|
runLabelTierMigration({
|
|
67415
68138
|
store: api.settingsStore,
|
|
@@ -67481,7 +68204,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
67481
68204
|
try {
|
|
67482
68205
|
locations = await api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION });
|
|
67483
68206
|
} catch (err) {
|
|
67484
|
-
logger.debug("pipeline-analytics: eventMedia location list unavailable — writing through the default ref", { meta: { error: errMsg(err) } });
|
|
68207
|
+
logger.debug("pipeline-analytics: eventMedia location list unavailable — writing through the default ref", { meta: { error: errMsg$1(err) } });
|
|
67485
68208
|
this.eventMediaWriteLocationCache = DEFAULT_MEDIA_LOCATION;
|
|
67486
68209
|
return DEFAULT_MEDIA_LOCATION;
|
|
67487
68210
|
}
|
|
@@ -67649,6 +68372,24 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
67649
68372
|
store: api.settingsStore,
|
|
67650
68373
|
logger: logger.child("SensorEventStore")
|
|
67651
68374
|
});
|
|
68375
|
+
this.summaryStore = new SummaryStore({
|
|
68376
|
+
store: api.settingsStore,
|
|
68377
|
+
logger: this.ctx.logger.child("summary"),
|
|
68378
|
+
onEvict: async (ids) => {
|
|
68379
|
+
await this.mediaStore?.deleteForOwner("summary", ids);
|
|
68380
|
+
}
|
|
68381
|
+
});
|
|
68382
|
+
this.summarySealer = new SummarySealer({
|
|
68383
|
+
summaries: this.summaryStore,
|
|
68384
|
+
openTrackIds: async (deviceId) => this.residents.trackIdsForDevice(deviceId),
|
|
68385
|
+
isCameraQuiet: async (deviceId) => (this.stationaryRegistry?.count(deviceId) ?? 0) === 0,
|
|
68386
|
+
now: () => Date.now(),
|
|
68387
|
+
logger: this.ctx.logger.child("summary-sealer"),
|
|
68388
|
+
onSealed: (summaryId, members) => {
|
|
68389
|
+
this.groupShots.forget(summaryId);
|
|
68390
|
+
for (const trackId of members) this.recognizedTrackIds.delete(trackId);
|
|
68391
|
+
}
|
|
68392
|
+
});
|
|
67652
68393
|
this.packageDropDetector = new PackageDropDetector({
|
|
67653
68394
|
events: eventStore,
|
|
67654
68395
|
emit: {
|
|
@@ -67686,7 +68427,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
67686
68427
|
onPersisted: (ev, phase) => this.notificationCenter?.onPackageEventPersisted(ev, phase),
|
|
67687
68428
|
onError: (scope, err) => logger.warn("package-drop detector error", { meta: {
|
|
67688
68429
|
scope,
|
|
67689
|
-
error: errMsg(err)
|
|
68430
|
+
error: errMsg$1(err)
|
|
67690
68431
|
} })
|
|
67691
68432
|
});
|
|
67692
68433
|
this.linkedCamerasCache = new LinkedCamerasCache({
|
|
@@ -67700,7 +68441,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
67700
68441
|
linkedDevicesBatch: { getLinkedDevicesBatch: (input) => api.deviceManager.getLinkedDevicesBatch.query(input) },
|
|
67701
68442
|
onError: (scope, err) => logger.warn("linked-cameras cache refresh failed", { meta: {
|
|
67702
68443
|
scope,
|
|
67703
|
-
error: errMsg(err)
|
|
68444
|
+
error: errMsg$1(err)
|
|
67704
68445
|
} })
|
|
67705
68446
|
});
|
|
67706
68447
|
const syntheticTracks = new SyntheticTrackMaterializer({
|
|
@@ -67709,7 +68450,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
67709
68450
|
tracks: trackStore,
|
|
67710
68451
|
onError: (scope, err) => logger.warn("synthetic track error", { meta: {
|
|
67711
68452
|
scope,
|
|
67712
|
-
error: errMsg(err)
|
|
68453
|
+
error: errMsg$1(err)
|
|
67713
68454
|
} })
|
|
67714
68455
|
});
|
|
67715
68456
|
this.syntheticTracks = syntheticTracks;
|
|
@@ -67721,7 +68462,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
67721
68462
|
...deviceId !== void 0 ? { tags: { deviceId } } : {},
|
|
67722
68463
|
meta: {
|
|
67723
68464
|
scope,
|
|
67724
|
-
error: errMsg(err)
|
|
68465
|
+
error: errMsg$1(err)
|
|
67725
68466
|
}
|
|
67726
68467
|
})
|
|
67727
68468
|
});
|
|
@@ -67731,7 +68472,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
67731
68472
|
logger: logger.child("AudioMarkers"),
|
|
67732
68473
|
onError: (scope, err) => logger.warn("audio marker error", { meta: {
|
|
67733
68474
|
scope,
|
|
67734
|
-
error: errMsg(err)
|
|
68475
|
+
error: errMsg$1(err)
|
|
67735
68476
|
} })
|
|
67736
68477
|
});
|
|
67737
68478
|
const galleryVectorStore = api.vectorStore;
|
|
@@ -68052,7 +68793,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68052
68793
|
} catch (err) {
|
|
68053
68794
|
this.ctx.logger.debug("zone ownership read failed", {
|
|
68054
68795
|
tags: { deviceId },
|
|
68055
|
-
meta: { error: errMsg(err) }
|
|
68796
|
+
meta: { error: errMsg$1(err) }
|
|
68056
68797
|
});
|
|
68057
68798
|
}
|
|
68058
68799
|
return out;
|
|
@@ -68112,7 +68853,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68112
68853
|
} catch (err) {
|
|
68113
68854
|
return {
|
|
68114
68855
|
ok: false,
|
|
68115
|
-
error: errMsg(err)
|
|
68856
|
+
error: errMsg$1(err)
|
|
68116
68857
|
};
|
|
68117
68858
|
}
|
|
68118
68859
|
},
|
|
@@ -68132,7 +68873,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68132
68873
|
} catch (err) {
|
|
68133
68874
|
return {
|
|
68134
68875
|
ok: false,
|
|
68135
|
-
error: errMsg(err)
|
|
68876
|
+
error: errMsg$1(err)
|
|
68136
68877
|
};
|
|
68137
68878
|
}
|
|
68138
68879
|
},
|
|
@@ -68488,7 +69229,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68488
69229
|
} catch (err) {
|
|
68489
69230
|
this.ctx.logger.warn("readEventThumbnail failed", { meta: {
|
|
68490
69231
|
eventId: id,
|
|
68491
|
-
error: errMsg(err)
|
|
69232
|
+
error: errMsg$1(err)
|
|
68492
69233
|
} });
|
|
68493
69234
|
throw err;
|
|
68494
69235
|
}
|
|
@@ -68496,6 +69237,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68496
69237
|
this.eventMediaDataPlane = await this.ctx.dataPlane?.serve({
|
|
68497
69238
|
prefix: "event-media",
|
|
68498
69239
|
access: planeAccess("event-media"),
|
|
69240
|
+
share: ["events-view"],
|
|
68499
69241
|
handler
|
|
68500
69242
|
}) ?? null;
|
|
68501
69243
|
this.eventMediaBaseUrl = this.eventMediaDataPlane !== null ? `/addon/${this.ctx.id}/event-media` : null;
|
|
@@ -68508,7 +69250,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68508
69250
|
access: planeAccess("event-media")
|
|
68509
69251
|
} });
|
|
68510
69252
|
} catch (err) {
|
|
68511
|
-
this.ctx.logger.warn("event-media data-plane failed to serve", { meta: { error: errMsg(err) } });
|
|
69253
|
+
this.ctx.logger.warn("event-media data-plane failed to serve", { meta: { error: errMsg$1(err) } });
|
|
68512
69254
|
}
|
|
68513
69255
|
}
|
|
68514
69256
|
/**
|
|
@@ -68534,7 +69276,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68534
69276
|
handler
|
|
68535
69277
|
}) ?? null;
|
|
68536
69278
|
} catch (err) {
|
|
68537
|
-
this.ctx.logger.warn("training-export data-plane failed to serve", { meta: { error: errMsg(err) } });
|
|
69279
|
+
this.ctx.logger.warn("training-export data-plane failed to serve", { meta: { error: errMsg$1(err) } });
|
|
68538
69280
|
}
|
|
68539
69281
|
}
|
|
68540
69282
|
/**
|
|
@@ -68835,7 +69577,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68835
69577
|
handler
|
|
68836
69578
|
}) ?? null;
|
|
68837
69579
|
} catch (err) {
|
|
68838
|
-
this.ctx.logger.warn("debug-export data-plane failed to serve", { meta: { error: errMsg(err) } });
|
|
69580
|
+
this.ctx.logger.warn("debug-export data-plane failed to serve", { meta: { error: errMsg$1(err) } });
|
|
68839
69581
|
}
|
|
68840
69582
|
}
|
|
68841
69583
|
/**
|
|
@@ -68864,7 +69606,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68864
69606
|
handler
|
|
68865
69607
|
}) ?? null;
|
|
68866
69608
|
} catch (err) {
|
|
68867
|
-
this.ctx.logger.warn("retrain-export data-plane failed to serve", { meta: { error: errMsg(err) } });
|
|
69609
|
+
this.ctx.logger.warn("retrain-export data-plane failed to serve", { meta: { error: errMsg$1(err) } });
|
|
68868
69610
|
}
|
|
68869
69611
|
}
|
|
68870
69612
|
/**
|
|
@@ -68929,7 +69671,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68929
69671
|
tags: { deviceId },
|
|
68930
69672
|
meta: {
|
|
68931
69673
|
deviceId,
|
|
68932
|
-
error: errMsg(err)
|
|
69674
|
+
error: errMsg$1(err)
|
|
68933
69675
|
}
|
|
68934
69676
|
});
|
|
68935
69677
|
return null;
|
|
@@ -69041,7 +69783,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
69041
69783
|
}, RETENTION_SWEEP_INTERVAL_MS);
|
|
69042
69784
|
this.ltsFlushTimer = setInterval(() => {
|
|
69043
69785
|
this.lts?.flushDue().catch((err) => {
|
|
69044
|
-
this.ctx.logger.warn("lts flush failed", { meta: { error: errMsg(err) } });
|
|
69786
|
+
this.ctx.logger.warn("lts flush failed", { meta: { error: errMsg$1(err) } });
|
|
69045
69787
|
});
|
|
69046
69788
|
}, LTS_FLUSH_INTERVAL_MS);
|
|
69047
69789
|
this.startOrphanAuditTimer();
|
|
@@ -69062,7 +69804,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
69062
69804
|
} catch (err) {
|
|
69063
69805
|
this.ctx.logger.error("artifacts storage location is NOT available — enrolling a face, a plate or a scene reference will FAIL until it is", { meta: {
|
|
69064
69806
|
location: ARTIFACTS_MEDIA_LOCATION,
|
|
69065
|
-
error: errMsg(err),
|
|
69807
|
+
error: errMsg$1(err),
|
|
69066
69808
|
hint: "the declaration is seeded when a storage-provider registers; restart this node so the manifest is re-read, or create an artifacts location. Refusing the write is deliberate — the footage volume is where the last gallery was lost (D197), and the addon data dir carries no media (D341)."
|
|
69067
69809
|
} });
|
|
69068
69810
|
return;
|
|
@@ -69079,7 +69821,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
69079
69821
|
hint: "the pixels are gone for good; re-enrol these people from a photo"
|
|
69080
69822
|
} });
|
|
69081
69823
|
} catch (err) {
|
|
69082
|
-
this.ctx.logger.warn("enrolled gallery media reconcile failed", { meta: { error: errMsg(err) } });
|
|
69824
|
+
this.ctx.logger.warn("enrolled gallery media reconcile failed", { meta: { error: errMsg$1(err) } });
|
|
69083
69825
|
}
|
|
69084
69826
|
try {
|
|
69085
69827
|
const identityStore = this.identityStore;
|
|
@@ -69111,7 +69853,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
69111
69853
|
maxReclaimPerRun: 16
|
|
69112
69854
|
})).reclaimed > 0) await this.faceRecognizer?.refreshGallery();
|
|
69113
69855
|
} catch (err) {
|
|
69114
|
-
this.ctx.logger.warn("gallery sample reconcile failed", { meta: { error: errMsg(err) } });
|
|
69856
|
+
this.ctx.logger.warn("gallery sample reconcile failed", { meta: { error: errMsg$1(err) } });
|
|
69115
69857
|
}
|
|
69116
69858
|
}
|
|
69117
69859
|
/** Schedule the orphan audit on the operator's interval (hours). */
|
|
@@ -69253,7 +69995,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
69253
69995
|
engine.start();
|
|
69254
69996
|
this.sceneEngine = engine;
|
|
69255
69997
|
this.sceneReloadTimer = setInterval(() => {
|
|
69256
|
-
store.load().then(() => store.pruneLatches()).catch((err) => log.debug("scene reload failed", { meta: { error: errMsg(err) } }));
|
|
69998
|
+
store.load().then(() => store.pruneLatches()).catch((err) => log.debug("scene reload failed", { meta: { error: errMsg$1(err) } }));
|
|
69257
69999
|
}, SCENE_RELOAD_INTERVAL_MS);
|
|
69258
70000
|
this.sceneReloadTimer.unref?.();
|
|
69259
70001
|
}
|
|
@@ -69297,7 +70039,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
69297
70039
|
lng: status.location.longitude
|
|
69298
70040
|
};
|
|
69299
70041
|
} catch (err) {
|
|
69300
|
-
this.ctx.logger.debug("site location read failed", { meta: { error: errMsg(err) } });
|
|
70042
|
+
this.ctx.logger.debug("site location read failed", { meta: { error: errMsg$1(err) } });
|
|
69301
70043
|
}
|
|
69302
70044
|
this.siteCoordsCache = {
|
|
69303
70045
|
value,
|
|
@@ -69316,7 +70058,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
69316
70058
|
} catch (err) {
|
|
69317
70059
|
this.ctx.logger.debug("scene slice mirror failed", {
|
|
69318
70060
|
tags: { deviceId },
|
|
69319
|
-
meta: { error: errMsg(err) }
|
|
70061
|
+
meta: { error: errMsg$1(err) }
|
|
69320
70062
|
});
|
|
69321
70063
|
}
|
|
69322
70064
|
}
|
|
@@ -69861,6 +70603,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
69861
70603
|
className: t.className
|
|
69862
70604
|
}
|
|
69863
70605
|
});
|
|
70606
|
+
this.summarySealer?.onTrackOpened({
|
|
70607
|
+
deviceId,
|
|
70608
|
+
trackId: id
|
|
70609
|
+
});
|
|
69864
70610
|
const startPayload = buildTrackLifecyclePayload({
|
|
69865
70611
|
deviceId,
|
|
69866
70612
|
trackId: id,
|
|
@@ -70078,6 +70824,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70078
70824
|
for (const crops of childCropsByEvent.values()) for (const c of crops) if (c.kind === "faceCrop") faceCrops += 1;
|
|
70079
70825
|
else plateCrops += 1;
|
|
70080
70826
|
const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight);
|
|
70827
|
+
this.offerGroupShot(deviceId, result, frameHandle);
|
|
70081
70828
|
for (const retry of this.collectFirstFrameRetries(deviceId, result.tracked, result.timestamp, firstFrameTargets)) firstFrameTargets.push(retry);
|
|
70082
70829
|
const targetedThisFrame = new Set([...firstFrameTargets.map((t) => t.trackId), ...snapshotTargets.map((s) => s.trackId)]);
|
|
70083
70830
|
const rasterFallbackCandidates = [];
|
|
@@ -70389,7 +71136,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70389
71136
|
} catch (err) {
|
|
70390
71137
|
this.ctx.logger.debug("confirmation gate: getGlobalSteps failed", { meta: {
|
|
70391
71138
|
nodeId,
|
|
70392
|
-
error: errMsg(err)
|
|
71139
|
+
error: errMsg$1(err)
|
|
70393
71140
|
} });
|
|
70394
71141
|
}
|
|
70395
71142
|
this.confirmationDetectorStepByNode.set(nodeId, {
|
|
@@ -70424,7 +71171,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70424
71171
|
} catch (err) {
|
|
70425
71172
|
this.ctx.logger.debug("confirmation gate: re-detection failed — fail-open", {
|
|
70426
71173
|
tags: { deviceId },
|
|
70427
|
-
meta: { error: errMsg(err) }
|
|
71174
|
+
meta: { error: errMsg$1(err) }
|
|
70428
71175
|
});
|
|
70429
71176
|
return null;
|
|
70430
71177
|
}
|
|
@@ -70728,7 +71475,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70728
71475
|
meta: {
|
|
70729
71476
|
trackId,
|
|
70730
71477
|
stepId: d.stepId,
|
|
70731
|
-
error: errMsg(err)
|
|
71478
|
+
error: errMsg$1(err)
|
|
70732
71479
|
}
|
|
70733
71480
|
});
|
|
70734
71481
|
}
|
|
@@ -70834,7 +71581,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70834
71581
|
try {
|
|
70835
71582
|
value = (await this.ctx.api.embeddingEncoder.getInfo.query())?.modelId ?? null;
|
|
70836
71583
|
} catch (err) {
|
|
70837
|
-
this.ctx.logger.debug("resolveClipModelId: getInfo failed", { meta: { error: errMsg(err) } });
|
|
71584
|
+
this.ctx.logger.debug("resolveClipModelId: getInfo failed", { meta: { error: errMsg$1(err) } });
|
|
70838
71585
|
}
|
|
70839
71586
|
this.clipModelIdCache = {
|
|
70840
71587
|
value,
|
|
@@ -70892,7 +71639,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70892
71639
|
tags: { deviceId },
|
|
70893
71640
|
meta: {
|
|
70894
71641
|
trackId: t.trackId,
|
|
70895
|
-
error: errMsg(err)
|
|
71642
|
+
error: errMsg$1(err)
|
|
70896
71643
|
}
|
|
70897
71644
|
});
|
|
70898
71645
|
}
|
|
@@ -70960,6 +71707,63 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70960
71707
|
* is the scheduling (coalescing, priority, budget) and the bookkeeping the
|
|
70961
71708
|
* retry gate reads.
|
|
70962
71709
|
*/
|
|
71710
|
+
/**
|
|
71711
|
+
* Offer one processed frame to the camera's OPEN summary (D359 phase 3).
|
|
71712
|
+
* Metadata only, at frame-rate: the frame is scored against the members the
|
|
71713
|
+
* sealer has mirrored, and the pixels are fetched — by handle, at the owning
|
|
71714
|
+
* node, as a native key frame — only when this frame beats the best so far.
|
|
71715
|
+
*/
|
|
71716
|
+
offerGroupShot(deviceId, result, frameHandle) {
|
|
71717
|
+
const open = this.summarySealer?.openFor(deviceId);
|
|
71718
|
+
if (!open || frameHandle === void 0) return;
|
|
71719
|
+
const score = scoreSummaryFrame({
|
|
71720
|
+
members: open.members,
|
|
71721
|
+
frameWidth: result.frameWidth,
|
|
71722
|
+
frameHeight: result.frameHeight,
|
|
71723
|
+
detections: result.tracked.map((t) => ({
|
|
71724
|
+
trackId: t.trackId,
|
|
71725
|
+
bbox: {
|
|
71726
|
+
x: t.bbox.x,
|
|
71727
|
+
y: t.bbox.y,
|
|
71728
|
+
width: t.bbox.w,
|
|
71729
|
+
height: t.bbox.h
|
|
71730
|
+
},
|
|
71731
|
+
confidence: t.confidence,
|
|
71732
|
+
recognized: this.recognizedTrackIds.has(t.trackId)
|
|
71733
|
+
}))
|
|
71734
|
+
});
|
|
71735
|
+
if (!this.groupShots.offer(open.id, score, { timestamp: result.timestamp })) return;
|
|
71736
|
+
const getNative = this.getNativeKeyFrameRgb;
|
|
71737
|
+
const mediaStore = this.mediaStore;
|
|
71738
|
+
if (!getNative || !mediaStore) return;
|
|
71739
|
+
const summaryId = open.id;
|
|
71740
|
+
this.captureScheduler.request({
|
|
71741
|
+
deviceId,
|
|
71742
|
+
kind: "keyFrame",
|
|
71743
|
+
coalesceKey: `summary:${summaryId}:keyFrame`,
|
|
71744
|
+
exec: () => persistSummaryGroupShot({
|
|
71745
|
+
fetchNativeFullFrame: getNative,
|
|
71746
|
+
encodeVariants: encodeKeyFrameVariants,
|
|
71747
|
+
media: mediaStore,
|
|
71748
|
+
logger: this.ctx.logger
|
|
71749
|
+
}, {
|
|
71750
|
+
deviceId,
|
|
71751
|
+
summaryId,
|
|
71752
|
+
timestamp: result.timestamp,
|
|
71753
|
+
frameHandle,
|
|
71754
|
+
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH
|
|
71755
|
+
})
|
|
71756
|
+
}).then((outcome) => {
|
|
71757
|
+
if (outcome?.kind === "landed") this.ctx.logger.debug("summary group shot landed", {
|
|
71758
|
+
tags: { deviceId },
|
|
71759
|
+
meta: {
|
|
71760
|
+
summaryId,
|
|
71761
|
+
visible: score.visible,
|
|
71762
|
+
quality: Number(score.quality.toFixed(3))
|
|
71763
|
+
}
|
|
71764
|
+
});
|
|
71765
|
+
}).catch(() => {});
|
|
71766
|
+
}
|
|
70963
71767
|
async persistKeyFrames(deviceId, timestamp, captures, frameHandle) {
|
|
70964
71768
|
const getNative = this.getNativeKeyFrameRgb;
|
|
70965
71769
|
const mediaStore = this.mediaStore;
|
|
@@ -71045,7 +71849,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71045
71849
|
meta: {
|
|
71046
71850
|
trackId,
|
|
71047
71851
|
keyFrameKey,
|
|
71048
|
-
error: errMsg(err)
|
|
71852
|
+
error: errMsg$1(err)
|
|
71049
71853
|
}
|
|
71050
71854
|
});
|
|
71051
71855
|
}
|
|
@@ -71104,6 +71908,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71104
71908
|
* FaceRecognizer's per-frame auto-match, and `routeDetailResults`' plate /
|
|
71105
71909
|
* fine-classification attribution. */
|
|
71106
71910
|
promoteSemanticRecognition(input) {
|
|
71911
|
+
this.recognizedTrackIds.add(input.trackId);
|
|
71107
71912
|
const ctx = this.ctxIfReady;
|
|
71108
71913
|
if (ctx === null) return;
|
|
71109
71914
|
promoteSemanticBestFrame({
|
|
@@ -71531,7 +72336,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71531
72336
|
tags: { deviceId: input.deviceId },
|
|
71532
72337
|
meta: {
|
|
71533
72338
|
className: ref.className,
|
|
71534
|
-
error: errMsg(err)
|
|
72339
|
+
error: errMsg$1(err)
|
|
71535
72340
|
}
|
|
71536
72341
|
});
|
|
71537
72342
|
}
|
|
@@ -71563,7 +72368,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71563
72368
|
tags: { deviceId: track.deviceId },
|
|
71564
72369
|
meta: {
|
|
71565
72370
|
episodeId: ref.episodeId,
|
|
71566
|
-
error: errMsg(err)
|
|
72371
|
+
error: errMsg$1(err)
|
|
71567
72372
|
}
|
|
71568
72373
|
});
|
|
71569
72374
|
}
|
|
@@ -71885,6 +72690,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71885
72690
|
async sweepExpiredTracks() {
|
|
71886
72691
|
if (this.shuttingDown || !this.trackStore) return;
|
|
71887
72692
|
await this.trackCloser.sweep();
|
|
72693
|
+
await this.summarySealer?.sweep();
|
|
71888
72694
|
}
|
|
71889
72695
|
/** Unified retention (Phase 1): earliest retained footage per device — the
|
|
71890
72696
|
* follow-recordings cutoff source. Cached 10 min: availability answers in
|
|
@@ -71935,7 +72741,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71935
72741
|
listTrackedDeviceIds: async () => await this.trackStore?.listDeviceIds() ?? []
|
|
71936
72742
|
}, (half, err) => this.ctx.logger.warn("retention scope half unavailable — sweeping on the other", { meta: {
|
|
71937
72743
|
half,
|
|
71938
|
-
error: errMsg(err)
|
|
72744
|
+
error: errMsg$1(err)
|
|
71939
72745
|
} }));
|
|
71940
72746
|
}
|
|
71941
72747
|
/**
|
|
@@ -71981,6 +72787,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71981
72787
|
await this.runTrackRetentionSweep(deviceIds, now);
|
|
71982
72788
|
const eventStore = this.eventStore;
|
|
71983
72789
|
const sensorEventStore = this.sensorEventStore;
|
|
72790
|
+
const summaryStore = this.summaryStore;
|
|
71984
72791
|
await sweepTracklessRoots({
|
|
71985
72792
|
deviceIds,
|
|
71986
72793
|
nowMs: now,
|
|
@@ -71990,15 +72797,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71990
72797
|
cutoffMs
|
|
71991
72798
|
}),
|
|
71992
72799
|
evictSensorEvents: sensorEventStore ? (deviceId, cutoffMs) => sensorEventStore.evictBefore(deviceId, cutoffMs) : null,
|
|
72800
|
+
evictSummaries: summaryStore ? (deviceId, cutoffMs) => summaryStore.evictBefore(deviceId, cutoffMs) : null,
|
|
71993
72801
|
onEvicted: async (deviceId, evicted, cutoffs) => {
|
|
71994
|
-
const total = evicted.motion.length + evicted.audio.length + evicted.sensor;
|
|
72802
|
+
const total = evicted.motion.length + evicted.audio.length + evicted.sensor + evicted.summaries;
|
|
71995
72803
|
await this.eventsOpsLog?.append({
|
|
71996
72804
|
op: "prune",
|
|
71997
72805
|
reason: "retention",
|
|
71998
72806
|
deviceId,
|
|
71999
72807
|
itemsAffected: total,
|
|
72000
72808
|
bytesReclaimed: 0,
|
|
72001
|
-
detail: `trackless age sweep (${cutoffs.effectiveMode}): ${evicted.motion.length} motion, ${evicted.audio.length} audio, ${evicted.sensor} sensor`
|
|
72809
|
+
detail: `trackless age sweep (${cutoffs.effectiveMode}): ${evicted.motion.length} motion, ${evicted.audio.length} audio, ${evicted.sensor} sensor, ${evicted.summaries} summaries`
|
|
72002
72810
|
});
|
|
72003
72811
|
this.ctx.logger.info("analytics trackless event eviction (age sweep)", {
|
|
72004
72812
|
tags: { deviceId },
|
|
@@ -72006,6 +72814,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
72006
72814
|
motion: evicted.motion.length,
|
|
72007
72815
|
audio: evicted.audio.length,
|
|
72008
72816
|
sensor: evicted.sensor,
|
|
72817
|
+
summaries: evicted.summaries,
|
|
72009
72818
|
mode: cutoffs.effectiveMode,
|
|
72010
72819
|
cutoffMs: cutoffs.cutoffMs
|
|
72011
72820
|
}
|
|
@@ -72825,7 +73634,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
72825
73634
|
const status = await api.pipelineOrchestrator.getCameraStatus.query({ deviceId: input.deviceId }).catch((err) => {
|
|
72826
73635
|
this.ctx.logger.warn("listEventKinds: getCameraStatus failed", {
|
|
72827
73636
|
tags: { deviceId: input.deviceId },
|
|
72828
|
-
meta: { error: errMsg(err) }
|
|
73637
|
+
meta: { error: errMsg$1(err) }
|
|
72829
73638
|
});
|
|
72830
73639
|
return null;
|
|
72831
73640
|
});
|
|
@@ -72844,7 +73653,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
72844
73653
|
tags: { deviceId: input.deviceId },
|
|
72845
73654
|
meta: {
|
|
72846
73655
|
scope,
|
|
72847
|
-
error: errMsg(err)
|
|
73656
|
+
error: errMsg$1(err)
|
|
72848
73657
|
}
|
|
72849
73658
|
})
|
|
72850
73659
|
}, input.deviceId);
|
|
@@ -72877,7 +73686,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
72877
73686
|
const statuses = await api.pipelineOrchestrator.getCameraStatuses.query({ deviceIds }).catch((err) => {
|
|
72878
73687
|
this.ctx.logger.warn("listEventKindsBatch: getCameraStatuses failed", { meta: {
|
|
72879
73688
|
deviceCount: deviceIds.length,
|
|
72880
|
-
error: errMsg(err)
|
|
73689
|
+
error: errMsg$1(err)
|
|
72881
73690
|
} });
|
|
72882
73691
|
return [];
|
|
72883
73692
|
});
|
|
@@ -72889,7 +73698,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
72889
73698
|
}, deviceIds).catch((err) => {
|
|
72890
73699
|
this.ctx.logger.warn("listEventKindsBatch: batched device topology unavailable — per-device fallback", { meta: {
|
|
72891
73700
|
deviceCount: deviceIds.length,
|
|
72892
|
-
error: errMsg(err)
|
|
73701
|
+
error: errMsg$1(err)
|
|
72893
73702
|
} });
|
|
72894
73703
|
return null;
|
|
72895
73704
|
});
|
|
@@ -72909,13 +73718,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
72909
73718
|
tags: { deviceId },
|
|
72910
73719
|
meta: {
|
|
72911
73720
|
scope,
|
|
72912
|
-
error: errMsg(err)
|
|
73721
|
+
error: errMsg$1(err)
|
|
72913
73722
|
}
|
|
72914
73723
|
})
|
|
72915
73724
|
}, deviceId);
|
|
72916
73725
|
}, (deviceId, err) => this.ctx.logger.warn("listEventKindsBatch: compose threw — empty vocabulary", {
|
|
72917
73726
|
tags: { deviceId },
|
|
72918
|
-
meta: { error: errMsg(err) }
|
|
73727
|
+
meta: { error: errMsg$1(err) }
|
|
72919
73728
|
}));
|
|
72920
73729
|
}
|
|
72921
73730
|
/** Per-camera sensor-event history (state changes of linked devices). */
|
|
@@ -72961,7 +73770,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
72961
73770
|
tags: { deviceId: data.deviceId },
|
|
72962
73771
|
meta: {
|
|
72963
73772
|
capName: data.capName,
|
|
72964
|
-
error: errMsg(err)
|
|
73773
|
+
error: errMsg$1(err)
|
|
72965
73774
|
}
|
|
72966
73775
|
});
|
|
72967
73776
|
}
|
|
@@ -73004,7 +73813,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
73004
73813
|
tags: { deviceId: data.deviceId },
|
|
73005
73814
|
meta: {
|
|
73006
73815
|
capName: data.capName,
|
|
73007
|
-
error: errMsg(err)
|
|
73816
|
+
error: errMsg$1(err)
|
|
73008
73817
|
}
|
|
73009
73818
|
});
|
|
73010
73819
|
}
|
|
@@ -73102,7 +73911,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
73102
73911
|
} catch (err) {
|
|
73103
73912
|
this.ctx?.logger?.debug("resolveProducingDeviceName failed", {
|
|
73104
73913
|
tags: { deviceId },
|
|
73105
|
-
meta: { error: errMsg(err) }
|
|
73914
|
+
meta: { error: errMsg$1(err) }
|
|
73106
73915
|
});
|
|
73107
73916
|
return;
|
|
73108
73917
|
}
|
|
@@ -73338,6 +74147,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
73338
74147
|
},
|
|
73339
74148
|
liveTrackIds: (ids) => trackStore.existingTrackIds(ids),
|
|
73340
74149
|
liveEventIds: (ids) => eventStore.existingEventIds(ids),
|
|
74150
|
+
liveSummaryIds: (ids) => {
|
|
74151
|
+
const summaries = this.summaryStore;
|
|
74152
|
+
if (!summaries) throw new Error("summary store not built — probe unavailable");
|
|
74153
|
+
return summaries.existingIds(ids);
|
|
74154
|
+
},
|
|
73341
74155
|
reclaim: async (collection, key) => {
|
|
73342
74156
|
if (collection === "pipeline-analytics:media") {
|
|
73343
74157
|
await mediaStore.deleteByKey(key);
|
|
@@ -73771,7 +74585,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
73771
74585
|
nodeId
|
|
73772
74586
|
};
|
|
73773
74587
|
} catch (err) {
|
|
73774
|
-
const reason = errMsg(err);
|
|
74588
|
+
const reason = errMsg$1(err);
|
|
73775
74589
|
failures.push(`${nodeId}: ${reason}`);
|
|
73776
74590
|
this.ctx.logger.warn("photo enrollment: a node could not analyse the photograph", { meta: {
|
|
73777
74591
|
nodeId,
|
|
@@ -74485,7 +75299,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
74485
75299
|
mediaStore,
|
|
74486
75300
|
faceStore: this.faceStore,
|
|
74487
75301
|
plateStore: this.plateStore,
|
|
74488
|
-
objectEmbeddingStore: this.objectEmbeddingStore
|
|
75302
|
+
objectEmbeddingStore: this.objectEmbeddingStore,
|
|
75303
|
+
summaryStore: this.summaryStore
|
|
74489
75304
|
};
|
|
74490
75305
|
const absent = absentCascadeLegs(stores);
|
|
74491
75306
|
if (absent.length > 0) this.ctx.logger.warn("track cascade is missing a leg — its rows will be left behind", { meta: {
|
|
@@ -74971,7 +75786,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
74971
75786
|
if (!file) return null;
|
|
74972
75787
|
return {
|
|
74973
75788
|
bytes: Buffer.from(file.base64, "base64"),
|
|
74974
|
-
key: file.key
|
|
75789
|
+
key: file.key,
|
|
75790
|
+
deviceId: file.deviceId
|
|
74975
75791
|
};
|
|
74976
75792
|
}
|
|
74977
75793
|
/**
|
|
@@ -74985,13 +75801,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
74985
75801
|
try {
|
|
74986
75802
|
return {
|
|
74987
75803
|
bytes: await makeSquareThumb(media.bytes, variant.size),
|
|
74988
|
-
key: `${media.key}|t${variant.size}
|
|
75804
|
+
key: `${media.key}|t${variant.size}`,
|
|
75805
|
+
deviceId: media.deviceId
|
|
74989
75806
|
};
|
|
74990
75807
|
} catch (err) {
|
|
74991
75808
|
this.ctx.logger.debug("event media: thumb variant failed — serving full", { meta: {
|
|
74992
75809
|
key: media.key,
|
|
74993
75810
|
size: variant.size,
|
|
74994
|
-
error: errMsg(err)
|
|
75811
|
+
error: errMsg$1(err)
|
|
74995
75812
|
} });
|
|
74996
75813
|
return media;
|
|
74997
75814
|
}
|
|
@@ -75015,7 +75832,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75015
75832
|
const file = await (this.mediaStore?.getByKey(key) ?? Promise.resolve(null));
|
|
75016
75833
|
return file === null ? null : {
|
|
75017
75834
|
bytes: Buffer.from(file.base64, "base64"),
|
|
75018
|
-
key: file.key
|
|
75835
|
+
key: file.key,
|
|
75836
|
+
deviceId: file.deviceId
|
|
75019
75837
|
};
|
|
75020
75838
|
};
|
|
75021
75839
|
const eventFiles = await listInfo("event", id);
|