@camstack/addon-post-analysis 1.2.270 → 1.2.272
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-DsMoBsZn.mjs → dist-4BOLDrB1.mjs} +366 -15
- package/dist/{dist-C-nKNO5A.js → dist-BkBZcyAK.js} +366 -15
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-kmC6aaLp.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D2l0wRbH.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BePOftX5.mjs +26 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DJJiuYPX.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-Do_HGiCP.mjs → hostInit-DW0buByp.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +401 -77
- package/dist/pipeline-analytics/index.mjs +401 -77
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-VNIUqm1k.mjs +0 -26
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-C5WB_ysd.mjs +0 -26
|
@@ -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-BkBZcyAK.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");
|
|
@@ -30055,10 +30055,31 @@ var MediaStore = class {
|
|
|
30055
30055
|
* was simply never returned, which is why every retention ops-log row this
|
|
30056
30056
|
* addon has ever written says `bytesReclaimed: 0` (D161).
|
|
30057
30057
|
*/
|
|
30058
|
-
async deleteForOwnerCounted(ownerKind, ownerIds) {
|
|
30059
|
-
return this.gated(() => this.deleteForOwnerUnchecked(ownerKind, ownerIds));
|
|
30058
|
+
async deleteForOwnerCounted(ownerKind, ownerIds, spare) {
|
|
30059
|
+
return this.gated(() => this.deleteForOwnerUnchecked(ownerKind, ownerIds, spare));
|
|
30060
30060
|
}
|
|
30061
|
-
|
|
30061
|
+
/**
|
|
30062
|
+
* Who owns a media row — kind and id — or `null` for an unknown id. The
|
|
30063
|
+
* stationary registry asks this before releasing an entry's key frame
|
|
30064
|
+
* (D547): the row is still `track`-owned by a track whose row is gone, and
|
|
30065
|
+
* only when no track row owns it is the entry the last thing vouching for it.
|
|
30066
|
+
*/
|
|
30067
|
+
async ownerOf(mediaId) {
|
|
30068
|
+
const row = await this.store.get.query({
|
|
30069
|
+
collection: MEDIA_COLLECTION,
|
|
30070
|
+
key: String(mediaId)
|
|
30071
|
+
});
|
|
30072
|
+
if (!row) return null;
|
|
30073
|
+
const data = row;
|
|
30074
|
+
const ownerKind = String(data["ownerKind"] ?? "");
|
|
30075
|
+
const ownerId = String(data["ownerId"] ?? "");
|
|
30076
|
+
if (!isMediaOwnerKind(ownerKind) || ownerId === "") return null;
|
|
30077
|
+
return {
|
|
30078
|
+
ownerKind,
|
|
30079
|
+
ownerId
|
|
30080
|
+
};
|
|
30081
|
+
}
|
|
30082
|
+
async deleteForOwnerUnchecked(ownerKind, ownerIds, spare) {
|
|
30062
30083
|
let rows = 0;
|
|
30063
30084
|
let bytes = 0;
|
|
30064
30085
|
for (const ownerId of ownerIds) {
|
|
@@ -30070,6 +30091,7 @@ var MediaStore = class {
|
|
|
30070
30091
|
} }
|
|
30071
30092
|
});
|
|
30072
30093
|
for (const row of found) {
|
|
30094
|
+
if (spare !== void 0 && spare.has(Number(row.id))) continue;
|
|
30073
30095
|
const rowData = row.data;
|
|
30074
30096
|
const path = String(rowData["path"] ?? "");
|
|
30075
30097
|
const sizeBytes = Number(rowData["sizeBytes"]);
|
|
@@ -30388,16 +30410,17 @@ var MediaStore = class {
|
|
|
30388
30410
|
* `deleteForOwner` is best-effort per row, so one bad row can't abort the
|
|
30389
30411
|
* batch.
|
|
30390
30412
|
*/
|
|
30391
|
-
async deleteByTracks(trackIds) {
|
|
30413
|
+
async deleteByTracks(trackIds, opts) {
|
|
30414
|
+
const spare = opts?.spare;
|
|
30392
30415
|
const own = await this.gated(async () => {
|
|
30393
|
-
const tracks = await this.deleteForOwnerUnchecked("track", trackIds);
|
|
30394
|
-
const faces = await this.deleteForOwnerUnchecked("face", trackIds.map((id) => FACE_MEDIA_OWNER_PREFIX + id));
|
|
30416
|
+
const tracks = await this.deleteForOwnerUnchecked("track", trackIds, spare);
|
|
30417
|
+
const faces = await this.deleteForOwnerUnchecked("face", trackIds.map((id) => FACE_MEDIA_OWNER_PREFIX + id), spare);
|
|
30395
30418
|
return {
|
|
30396
30419
|
rows: tracks.rows + faces.rows,
|
|
30397
30420
|
bytes: tracks.bytes + faces.bytes
|
|
30398
30421
|
};
|
|
30399
30422
|
});
|
|
30400
|
-
const plates = await this.deleteForOwnerCounted("plate", trackIds.map((id) => PLATE_MEDIA_OWNER_PREFIX + id));
|
|
30423
|
+
const plates = await this.deleteForOwnerCounted("plate", trackIds.map((id) => PLATE_MEDIA_OWNER_PREFIX + id), spare);
|
|
30401
30424
|
return [{
|
|
30402
30425
|
family: "media",
|
|
30403
30426
|
rows: own.rows + plates.rows,
|
|
@@ -39386,7 +39409,9 @@ var FrameProcessor = class {
|
|
|
39386
39409
|
const refusals = [];
|
|
39387
39410
|
const labelPatchByTrack = /* @__PURE__ */ new Map();
|
|
39388
39411
|
const tracked = trackedDetections.map((td) => {
|
|
39389
|
-
const
|
|
39412
|
+
const objectState = objectStates.find((o) => o.trackId === td.trackId);
|
|
39413
|
+
const state = mapObjectStateToTrackState(objectState?.state);
|
|
39414
|
+
const settling = objectState?.stationarySince !== void 0 && state !== "idle";
|
|
39390
39415
|
const candidate = resolveDetectionLabelCandidate({
|
|
39391
39416
|
className: td.class,
|
|
39392
39417
|
originalClass: td.originalClass,
|
|
@@ -39424,6 +39449,7 @@ var FrameProcessor = class {
|
|
|
39424
39449
|
zones: zonesByTrack.get(td.trackId) ?? [],
|
|
39425
39450
|
state,
|
|
39426
39451
|
matchedThisFrame: td.matchedThisFrame !== false,
|
|
39452
|
+
settling,
|
|
39427
39453
|
...td.resurrectionScene !== void 0 ? { resurrectionScene: td.resurrectionScene } : {},
|
|
39428
39454
|
...labelPatch !== void 0 ? { labelPatch } : {},
|
|
39429
39455
|
...emb !== void 0 ? {
|
|
@@ -40419,8 +40445,16 @@ var TrackCloser = class {
|
|
|
40419
40445
|
try {
|
|
40420
40446
|
const trackStore = this.deps.trackStore();
|
|
40421
40447
|
if (!trackStore) return;
|
|
40422
|
-
const
|
|
40448
|
+
const discarded = [];
|
|
40449
|
+
const discardTracks = this.deps.discardTracks;
|
|
40450
|
+
const expired = await trackStore.expireStale(this.now(), {
|
|
40451
|
+
...this.deps.isKnownParked !== void 0 ? { isKnownParked: this.deps.isKnownParked } : {},
|
|
40452
|
+
...discardTracks !== void 0 ? { onDiscard: (t) => {
|
|
40453
|
+
discarded.push(t);
|
|
40454
|
+
} } : {}
|
|
40455
|
+
});
|
|
40423
40456
|
for (const t of expired) await this.closeExpired(t);
|
|
40457
|
+
if (discardTracks !== void 0 && discarded.length > 0) await discardTracks(discarded);
|
|
40424
40458
|
} catch (err) {
|
|
40425
40459
|
if (this.deps.isShuttingDown()) return;
|
|
40426
40460
|
this.deps.logger.debug("sweepExpiredTracks failed", { meta: { error: String(err) } });
|
|
@@ -44118,6 +44152,19 @@ var VEHICLE_CLASSES$1 = new Set([
|
|
|
44118
44152
|
"truck",
|
|
44119
44153
|
"bus"
|
|
44120
44154
|
]);
|
|
44155
|
+
/**
|
|
44156
|
+
* A short motionless PERSON is stamped and persisted, never discarded (D547).
|
|
44157
|
+
*
|
|
44158
|
+
* The discard exists for re-sightings of an OBJECT an entity represents — a
|
|
44159
|
+
* parked car the registry censuses in two sessions, a plate flash on it. No
|
|
44160
|
+
* entity ever represents a person who stood still for ten seconds (the person
|
|
44161
|
+
* policy needs thirty minutes of agreement, on purpose), and the face read
|
|
44162
|
+
* such a track holds is the only record of who was there. Deleting the track
|
|
44163
|
+
* would delete that; hiding the row from the feed was always the whole rule.
|
|
44164
|
+
*/
|
|
44165
|
+
function isDiscardExemptClass(className) {
|
|
44166
|
+
return className.toLowerCase() === "person";
|
|
44167
|
+
}
|
|
44121
44168
|
function motionlessMaxPxForClass(className) {
|
|
44122
44169
|
return VEHICLE_CLASSES$1.has(className.toLowerCase()) ? 8 : 40;
|
|
44123
44170
|
}
|
|
@@ -44875,7 +44922,30 @@ var TrackStore = class {
|
|
|
44875
44922
|
for (const [trackId, t] of this.active) {
|
|
44876
44923
|
if (nowMs - t.lastSeen < this.config.ttlMs) continue;
|
|
44877
44924
|
t.active = false;
|
|
44878
|
-
|
|
44925
|
+
const knownParked = t.positions.length > 0 && opts?.isKnownParked?.(t) === true;
|
|
44926
|
+
const shortMotionless = t.positions.length > 0 && isShortMotionlessTrack(t.lastSeen - t.firstSeen, netDisplacementPx(t.positions), t.className);
|
|
44927
|
+
const stillObject = knownParked || shortMotionless;
|
|
44928
|
+
const discardable = knownParked || shortMotionless && !isDiscardExemptClass(t.className);
|
|
44929
|
+
const onDiscard = opts?.onDiscard;
|
|
44930
|
+
if (discardable && onDiscard !== void 0) {
|
|
44931
|
+
const record = cloneTrack(t);
|
|
44932
|
+
this.active.delete(trackId);
|
|
44933
|
+
this.noteRowClosed(trackId, nowMs);
|
|
44934
|
+
this.logger.debug("track discarded on expiry — a still object is an entity, not a track", {
|
|
44935
|
+
tags: { deviceId: t.deviceId },
|
|
44936
|
+
meta: {
|
|
44937
|
+
deviceId: t.deviceId,
|
|
44938
|
+
trackId,
|
|
44939
|
+
className: t.className,
|
|
44940
|
+
lifeMs: t.lastSeen - t.firstSeen,
|
|
44941
|
+
netDisplacementPx: Math.round(netDisplacementPx(t.positions)),
|
|
44942
|
+
knownParked
|
|
44943
|
+
}
|
|
44944
|
+
});
|
|
44945
|
+
onDiscard(record);
|
|
44946
|
+
continue;
|
|
44947
|
+
}
|
|
44948
|
+
if (stillObject) t.closedReason = "stationary-promotion";
|
|
44879
44949
|
const record = cloneTrack(t);
|
|
44880
44950
|
try {
|
|
44881
44951
|
await this.persistCompleted(record);
|
|
@@ -47798,6 +47868,8 @@ function visitsToClips(visits, events, pad, vouched) {
|
|
|
47798
47868
|
* injected fetch/resolve deps so the compose logic is testable without I/O.
|
|
47799
47869
|
*/
|
|
47800
47870
|
var SOURCE = "analytics";
|
|
47871
|
+
/** The addon that registers this provider — stamped on its source row. */
|
|
47872
|
+
var ANALYTICS_ADDON_ID = "pipeline-analytics";
|
|
47801
47873
|
/**
|
|
47802
47874
|
* Backstop on the ANNOTATION read — never on the clip list.
|
|
47803
47875
|
*
|
|
@@ -47959,14 +48031,34 @@ function createVideoclipsProvider(deps) {
|
|
|
47959
48031
|
reportUnvouchedClips(deps, deviceId, answer);
|
|
47960
48032
|
return answer;
|
|
47961
48033
|
},
|
|
48034
|
+
/**
|
|
48035
|
+
* The one source this provider serves, on every camera: the hub's own
|
|
48036
|
+
* analytics visits over recorded footage.
|
|
48037
|
+
*
|
|
48038
|
+
* Always `ok`, and that is not optimism — this source IS the process
|
|
48039
|
+
* answering the call, so there is no reachability question to report. A
|
|
48040
|
+
* read that fails INSIDE `listClips` degrades that list (and logs it); a
|
|
48041
|
+
* source that cannot be reached at all is reported by the collection
|
|
48042
|
+
* dispatcher, which is the only layer that can tell the difference.
|
|
48043
|
+
* `label` is what the picker draws.
|
|
48044
|
+
*/
|
|
48045
|
+
listSources: async () => [{
|
|
48046
|
+
source: SOURCE,
|
|
48047
|
+
addonId: ANALYTICS_ADDON_ID,
|
|
48048
|
+
label: "CamStack recordings",
|
|
48049
|
+
availability: { state: "ok" }
|
|
48050
|
+
}],
|
|
47962
48051
|
getClipPlayback: async ({ deviceId, clipId }) => {
|
|
47963
48052
|
const parsed = parseClipId(clipId);
|
|
47964
48053
|
if (!parsed) throw new Error(`videoclips: unrecognized clipId "${clipId}"`);
|
|
47965
|
-
return
|
|
47966
|
-
|
|
47967
|
-
|
|
47968
|
-
|
|
47969
|
-
|
|
48054
|
+
return {
|
|
48055
|
+
...await deps.resolvePlayback({
|
|
48056
|
+
deviceId,
|
|
48057
|
+
startMs: parsed.startMs,
|
|
48058
|
+
endMs: parsed.endMs
|
|
48059
|
+
}),
|
|
48060
|
+
format: "hls"
|
|
48061
|
+
};
|
|
47970
48062
|
}
|
|
47971
48063
|
};
|
|
47972
48064
|
}
|
|
@@ -53487,6 +53579,7 @@ var FaceRecognizer = class {
|
|
|
53487
53579
|
zones: [],
|
|
53488
53580
|
state: "moving",
|
|
53489
53581
|
matchedThisFrame: true,
|
|
53582
|
+
settling: false,
|
|
53490
53583
|
embedding: input.embedding,
|
|
53491
53584
|
embeddingModelId: modelId,
|
|
53492
53585
|
...input.faceBbox !== void 0 ? { faceBbox: input.faceBbox } : {},
|
|
@@ -60881,64 +60974,53 @@ function stillnessVerdictFor(source, trackState) {
|
|
|
60881
60974
|
return source === "burst" ? void 0 : trackState;
|
|
60882
60975
|
}
|
|
60883
60976
|
/**
|
|
60884
|
-
*
|
|
60885
|
-
*
|
|
60886
|
-
* Two questions, deliberately kept apart:
|
|
60887
|
-
*
|
|
60888
|
-
* - IDENTITY — is this the same object in the same place? That is the IoU,
|
|
60889
|
-
* and it is the only notion of sameness in this module.
|
|
60890
|
-
* - STILLNESS — is it going anywhere? On a session frame that is the
|
|
60891
|
-
* TRACKER's verdict, and nothing here re-derives it. On a burst frame it is
|
|
60892
|
-
* the OCCASION: the runner rechecks a still scene and nothing else (see
|
|
60893
|
-
* {@link stillnessVerdictFor}).
|
|
60977
|
+
* What one observation is worth to a candidate: `agree` (N and M), `disagree`
|
|
60978
|
+
* (M only) or `undecided` (neither).
|
|
60894
60979
|
*
|
|
60895
|
-
* ##
|
|
60980
|
+
* ## Why a third answer exists (D547)
|
|
60896
60981
|
*
|
|
60897
|
-
*
|
|
60898
|
-
*
|
|
60899
|
-
* the
|
|
60900
|
-
*
|
|
60901
|
-
*
|
|
60902
|
-
*
|
|
60903
|
-
*
|
|
60904
|
-
*
|
|
60905
|
-
*
|
|
60906
|
-
*
|
|
60907
|
-
* no number of its own.
|
|
60982
|
+
* The ratio N/M exists to dilute a car that merely drove through the spot.
|
|
60983
|
+
* It was also diluting a car that had not yet had the CHANCE to be called
|
|
60984
|
+
* still: the five `entered` frames every track begins with, and every frame
|
|
60985
|
+
* on which the analyzer's settled test already passed but
|
|
60986
|
+
* `stationaryThresholdSec` had not elapsed — `moving` in name only. On a
|
|
60987
|
+
* session-dispatched camera those frames are a third of every 36 s track, and
|
|
60988
|
+
* a session cut short by a passer-by is made of nothing else. Measured on
|
|
60989
|
+
* device 617 (DNRG309, 2026-09-19 20:32 → 09-20 07:53): 13 of the 21 tracks
|
|
60990
|
+
* bound to the candidate never reached `idle`, the ledger's ratio was 0.493
|
|
60991
|
+
* against 0.6, and a parked car took 11.3 h and 19 tracks to be censused.
|
|
60908
60992
|
*
|
|
60909
|
-
*
|
|
60910
|
-
*
|
|
60911
|
-
*
|
|
60912
|
-
*
|
|
60913
|
-
* passing the edge is gone in ~8 frames, five of them `entered`, and 5/8 = 0.62
|
|
60914
|
-
* cleared the 0.6 ratio unaided — an entry born on an empty spot, retired 24
|
|
60915
|
-
* absent readings later, one "parked" and one "departed" notification for a
|
|
60916
|
-
* passage. A parked car's re-detection loses five observations of ~280.
|
|
60917
|
-
*
|
|
60918
|
-
* A session observation with NO verdict at all does not agree either. There
|
|
60919
|
-
* is no trackless producer any more (the hydrated baseline sampler is gone),
|
|
60920
|
-
* so an absent verdict on a session frame is a producer that forgot the
|
|
60921
|
-
* field — the one way D494 said this could rot — and it must read as a miss,
|
|
60922
|
-
* never as the old geometry-only rule.
|
|
60993
|
+
* "Not yet decided" is not evidence either way, so it counts as neither. A
|
|
60994
|
+
* subject that is TRAVELLING — `moving` with the settled test failing — still
|
|
60995
|
+
* dilutes, so the drive-through case D508 measured (5 `entered` of 8 frames)
|
|
60996
|
+
* now scores 0/3 instead of the 5/8 that once minted an entry on an empty spot.
|
|
60923
60997
|
*/
|
|
60924
|
-
function
|
|
60925
|
-
if (overlap < agreeIou) return
|
|
60926
|
-
if (source === "burst") return
|
|
60927
|
-
|
|
60998
|
+
function observationVote(overlap, agreeIou, source, movementState, settling) {
|
|
60999
|
+
if (overlap < agreeIou) return "disagree";
|
|
61000
|
+
if (source === "burst") return "agree";
|
|
61001
|
+
if (movementState === "idle") return "agree";
|
|
61002
|
+
if (movementState === "entered") return "undecided";
|
|
61003
|
+
if (movementState === "moving" && settling) return "undecided";
|
|
61004
|
+
return "disagree";
|
|
60928
61005
|
}
|
|
60929
61006
|
/**
|
|
60930
61007
|
* Fold one observation into a candidate.
|
|
60931
61008
|
*
|
|
60932
|
-
* `
|
|
61009
|
+
* The `vote` is decided by the CALLER, because M is what makes the ratio mean
|
|
60933
61010
|
* anything: once a track has bound to a candidate, every later observation of
|
|
60934
61011
|
* that track counts toward M whether it agrees or not. That is what dilutes a
|
|
60935
61012
|
* car that merely drove through the spot, and it is why this function cannot
|
|
60936
61013
|
* re-derive agreement from the box alone.
|
|
60937
61014
|
*/
|
|
60938
|
-
function foldObservation(row, observation,
|
|
60939
|
-
const seen = row.seen + 1;
|
|
61015
|
+
function foldObservation(row, observation, vote, overlap) {
|
|
60940
61016
|
const rotated = rotateWindow(row, observation.timestamp);
|
|
60941
|
-
if (
|
|
61017
|
+
if (vote === "undecided") return {
|
|
61018
|
+
...rotated,
|
|
61019
|
+
undecided: row.undecided + 1,
|
|
61020
|
+
updatedAt: observation.timestamp
|
|
61021
|
+
};
|
|
61022
|
+
const seen = row.seen + 1;
|
|
61023
|
+
if (vote === "disagree") return {
|
|
60942
61024
|
...rotated,
|
|
60943
61025
|
seen,
|
|
60944
61026
|
windowSeen: rotated.windowSeen + 1,
|
|
@@ -60986,6 +61068,7 @@ function newEvidenceRow(observation, anchorId) {
|
|
|
60986
61068
|
...observation.label !== void 0 ? { label: observation.label } : {},
|
|
60987
61069
|
sessionAgreed: observation.source === "session" ? 1 : 0,
|
|
60988
61070
|
burstAgreed: observation.source === "burst" ? 1 : 0,
|
|
61071
|
+
undecided: 0,
|
|
60989
61072
|
updatedAt: observation.timestamp
|
|
60990
61073
|
};
|
|
60991
61074
|
}
|
|
@@ -61058,6 +61141,10 @@ var STATIONARY_EVIDENCE_COLUMNS = [
|
|
|
61058
61141
|
name: "burstAgreed",
|
|
61059
61142
|
type: "INTEGER"
|
|
61060
61143
|
},
|
|
61144
|
+
{
|
|
61145
|
+
name: "undecided",
|
|
61146
|
+
type: "INTEGER"
|
|
61147
|
+
},
|
|
61061
61148
|
{
|
|
61062
61149
|
name: "updatedAt",
|
|
61063
61150
|
type: "INTEGER"
|
|
@@ -61105,6 +61192,9 @@ var StationaryEvidenceLedger = class {
|
|
|
61105
61192
|
* every observation of a box that never changes (3836's jacket is seen five
|
|
61106
61193
|
* times a second in a session), so the line is said once per candidate. */
|
|
61107
61194
|
implausibleReported = /* @__PURE__ */ new Set();
|
|
61195
|
+
/** `<deviceId>:<trackId>` of tracks whose first undecided fold was logged
|
|
61196
|
+
* (D547). Pruned with the bindings in {@link retainTracks}. */
|
|
61197
|
+
undecidedReported = /* @__PURE__ */ new Set();
|
|
61108
61198
|
anchorSeq = 0;
|
|
61109
61199
|
constructor(deps) {
|
|
61110
61200
|
this.logger = deps.logger;
|
|
@@ -61133,6 +61223,7 @@ var StationaryEvidenceLedger = class {
|
|
|
61133
61223
|
...row.label !== void 0 ? { label: row.label } : {},
|
|
61134
61224
|
sessionAgreed: row.sessionAgreed,
|
|
61135
61225
|
burstAgreed: row.burstAgreed,
|
|
61226
|
+
undecided: row.undecided,
|
|
61136
61227
|
updatedAt: row.updatedAt
|
|
61137
61228
|
}),
|
|
61138
61229
|
fromRecord: (key, data) => {
|
|
@@ -61166,6 +61257,7 @@ var StationaryEvidenceLedger = class {
|
|
|
61166
61257
|
...label !== void 0 ? { label } : {},
|
|
61167
61258
|
sessionAgreed: numberOr(data.sessionAgreed, 0),
|
|
61168
61259
|
burstAgreed: numberOr(data.burstAgreed, 0),
|
|
61260
|
+
undecided: numberOr(data.undecided, 0),
|
|
61169
61261
|
updatedAt
|
|
61170
61262
|
};
|
|
61171
61263
|
},
|
|
@@ -61300,8 +61392,10 @@ var StationaryEvidenceLedger = class {
|
|
|
61300
61392
|
}
|
|
61301
61393
|
if (bindKey !== void 0) this.binding.set(bindKey, row.key);
|
|
61302
61394
|
}
|
|
61303
|
-
const
|
|
61304
|
-
const
|
|
61395
|
+
const vote = observationVote(overlap, policy.agreeIou, observation.source, observation.movementState, observation.settling === true);
|
|
61396
|
+
const agrees = vote === "agree";
|
|
61397
|
+
if (vote === "undecided") this.noteUndecided(row, observation);
|
|
61398
|
+
const next = foldObservation(row, observation, vote, overlap);
|
|
61305
61399
|
this.ledger.stage(next);
|
|
61306
61400
|
this.dirty.add(next.key);
|
|
61307
61401
|
if (created) this.capDevice(observation.deviceId);
|
|
@@ -61351,6 +61445,10 @@ var StationaryEvidenceLedger = class {
|
|
|
61351
61445
|
if (!bindKey.startsWith(prefix)) continue;
|
|
61352
61446
|
if (!liveTrackIds.has(bindKey.slice(prefix.length))) this.binding.delete(bindKey);
|
|
61353
61447
|
}
|
|
61448
|
+
for (const key of [...this.undecidedReported]) {
|
|
61449
|
+
if (!key.startsWith(prefix)) continue;
|
|
61450
|
+
if (!liveTrackIds.has(key.slice(prefix.length))) this.undecidedReported.delete(key);
|
|
61451
|
+
}
|
|
61354
61452
|
}
|
|
61355
61453
|
forgetKey(key) {
|
|
61356
61454
|
this.forgetKeyDurable(key);
|
|
@@ -61363,6 +61461,32 @@ var StationaryEvidenceLedger = class {
|
|
|
61363
61461
|
* work is never silent (D391), and one that refuses it five times a second
|
|
61364
61462
|
* is never loud.
|
|
61365
61463
|
*/
|
|
61464
|
+
/**
|
|
61465
|
+
* An undecided observation is dropped from the tally on purpose (D547), and
|
|
61466
|
+
* a branch that drops work says so — once per track, at debug: the same
|
|
61467
|
+
* track offers one every frame until it decides, and a line per frame would
|
|
61468
|
+
* bury what it reports. The lifetime count rides the row (`undecided`).
|
|
61469
|
+
*/
|
|
61470
|
+
noteUndecided(row, observation) {
|
|
61471
|
+
const trackId = observation.trackId;
|
|
61472
|
+
if (trackId === void 0) return;
|
|
61473
|
+
const key = `${observation.deviceId}:${trackId}`;
|
|
61474
|
+
if (this.undecidedReported.has(key)) return;
|
|
61475
|
+
this.undecidedReported.add(key);
|
|
61476
|
+
this.logger.debug("stationary evidence: undecided observation — counted as neither N nor M", {
|
|
61477
|
+
tags: { deviceId: observation.deviceId },
|
|
61478
|
+
meta: {
|
|
61479
|
+
deviceId: observation.deviceId,
|
|
61480
|
+
trackId,
|
|
61481
|
+
candidateKey: row.key,
|
|
61482
|
+
movementState: observation.movementState ?? null,
|
|
61483
|
+
settling: observation.settling === true,
|
|
61484
|
+
agreed: row.agreed,
|
|
61485
|
+
seen: row.seen,
|
|
61486
|
+
undecided: row.undecided
|
|
61487
|
+
}
|
|
61488
|
+
});
|
|
61489
|
+
}
|
|
61366
61490
|
noteImplausible(row, frame) {
|
|
61367
61491
|
if (this.implausibleReported.has(row.key)) return;
|
|
61368
61492
|
this.implausibleReported.add(row.key);
|
|
@@ -61435,6 +61559,65 @@ var StationaryEvidenceLedger = class {
|
|
|
61435
61559
|
for (const row of excess) this.forgetKey(row.key);
|
|
61436
61560
|
}
|
|
61437
61561
|
};
|
|
61562
|
+
//#endregion
|
|
61563
|
+
//#region src/pipeline-analytics/pipeline/stationary/stationary-track-disposal.ts
|
|
61564
|
+
function planStationaryTrackDisposal(input) {
|
|
61565
|
+
const trackIds = [input.liveTrackId];
|
|
61566
|
+
if (input.sourceTrackId !== void 0 && input.sourceTrackId !== input.liveTrackId) trackIds.push(input.sourceTrackId);
|
|
61567
|
+
const keepMediaIds = /* @__PURE__ */ new Set();
|
|
61568
|
+
if (input.entryKeyFrameMediaId !== void 0) keepMediaIds.add(input.entryKeyFrameMediaId);
|
|
61569
|
+
return {
|
|
61570
|
+
trackIds,
|
|
61571
|
+
keepMediaIds
|
|
61572
|
+
};
|
|
61573
|
+
}
|
|
61574
|
+
/**
|
|
61575
|
+
* Run the plan. The live track is dropped from RAM FIRST so it can never be
|
|
61576
|
+
* persisted by a sweep racing this call; the cascade then removes what every
|
|
61577
|
+
* track owned, sparing the entry's key frame, and deletes the row (a no-op for
|
|
61578
|
+
* the never-persisted live track).
|
|
61579
|
+
*
|
|
61580
|
+
* Never silent: the line names the entity, what went and what was kept — a
|
|
61581
|
+
* deleted track is dropped work, and the operator asked for it by name.
|
|
61582
|
+
*/
|
|
61583
|
+
async function disposeStationaryTracks(deps, plan) {
|
|
61584
|
+
const [liveTrackId] = plan.trackIds;
|
|
61585
|
+
if (liveTrackId !== void 0) deps.trackStore.dropActive(liveTrackId);
|
|
61586
|
+
const sparingMedia = { deleteByTracks: (trackIds) => deps.media.deleteByTracks(trackIds, { spare: plan.keepMediaIds }) };
|
|
61587
|
+
const result = await runTrackCascadeBatch({
|
|
61588
|
+
registry: [...deps.leaves, sparingMedia],
|
|
61589
|
+
trackStore: deps.trackStore,
|
|
61590
|
+
deviceId: deps.deviceId,
|
|
61591
|
+
onTrackCleanup: deps.onTrackCleanup,
|
|
61592
|
+
onFailure: (trackId, err) => {
|
|
61593
|
+
deps.logger.warn("stationary promotion: a source track could not be disposed of", {
|
|
61594
|
+
tags: { deviceId: deps.deviceId },
|
|
61595
|
+
meta: {
|
|
61596
|
+
deviceId: deps.deviceId,
|
|
61597
|
+
entryId: deps.entryId,
|
|
61598
|
+
trackId,
|
|
61599
|
+
error: String(err)
|
|
61600
|
+
}
|
|
61601
|
+
});
|
|
61602
|
+
}
|
|
61603
|
+
}, plan.trackIds);
|
|
61604
|
+
deps.logger.info("stationary promotion: source tracks discarded — the entity keeps its key frame", {
|
|
61605
|
+
tags: { deviceId: deps.deviceId },
|
|
61606
|
+
meta: {
|
|
61607
|
+
deviceId: deps.deviceId,
|
|
61608
|
+
entryId: deps.entryId,
|
|
61609
|
+
trackIds: plan.trackIds,
|
|
61610
|
+
keptMediaIds: [...plan.keepMediaIds],
|
|
61611
|
+
deleted: result.deleted,
|
|
61612
|
+
failed: result.failed,
|
|
61613
|
+
mediaRows: result.tally.media
|
|
61614
|
+
}
|
|
61615
|
+
});
|
|
61616
|
+
return {
|
|
61617
|
+
deleted: result.deleted,
|
|
61618
|
+
failed: result.failed
|
|
61619
|
+
};
|
|
61620
|
+
}
|
|
61438
61621
|
/**
|
|
61439
61622
|
* How long the same weak box must keep being seen before it may re-anchor.
|
|
61440
61623
|
*
|
|
@@ -61614,6 +61797,7 @@ var StationaryObjectRegistry = class {
|
|
|
61614
61797
|
sessionGapMs;
|
|
61615
61798
|
warmUpMs;
|
|
61616
61799
|
ttlForDevice;
|
|
61800
|
+
onEntryDropped;
|
|
61617
61801
|
onChange;
|
|
61618
61802
|
requestConfirmationLook;
|
|
61619
61803
|
/**
|
|
@@ -61707,6 +61891,7 @@ var StationaryObjectRegistry = class {
|
|
|
61707
61891
|
this.sessionGapMs = deps.sessionGapMs ?? 5e3;
|
|
61708
61892
|
this.warmUpMs = deps.warmUpMs ?? 2e3;
|
|
61709
61893
|
this.ttlForDevice = deps.ttlForDevice;
|
|
61894
|
+
this.onEntryDropped = deps.onEntryDropped;
|
|
61710
61895
|
this.onChange = deps.onChange;
|
|
61711
61896
|
this.requestConfirmationLook = deps.requestConfirmationLook;
|
|
61712
61897
|
}
|
|
@@ -62316,6 +62501,7 @@ var StationaryObjectRegistry = class {
|
|
|
62316
62501
|
return at !== void 0 && at > entry.lastConfirmedAt;
|
|
62317
62502
|
}
|
|
62318
62503
|
dropEntry(deviceId, id) {
|
|
62504
|
+
const dropped = this.ledger.get(id);
|
|
62319
62505
|
this.idsByDevice.get(deviceId)?.delete(id);
|
|
62320
62506
|
this.dirty.delete(id);
|
|
62321
62507
|
this.weakRuns.delete(id);
|
|
@@ -62325,6 +62511,7 @@ var StationaryObjectRegistry = class {
|
|
|
62325
62511
|
if (armed.size === 0) this.departureStrikes.delete(deviceId);
|
|
62326
62512
|
}
|
|
62327
62513
|
this.ledger.forget(id);
|
|
62514
|
+
if (dropped !== void 0) this.onEntryDropped?.(dropped);
|
|
62328
62515
|
}
|
|
62329
62516
|
};
|
|
62330
62517
|
/** The persisted column map for an entry (the `id` PK is passed separately). */
|
|
@@ -68257,8 +68444,10 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
68257
68444
|
let liveTracks;
|
|
68258
68445
|
const liveEvents = /* @__PURE__ */ new Map();
|
|
68259
68446
|
let liveSummaries;
|
|
68447
|
+
let keptMedia = /* @__PURE__ */ new Set();
|
|
68260
68448
|
try {
|
|
68261
68449
|
liveTracks = trackIds.length > 0 ? await deps.liveTrackIds(trackIds) : /* @__PURE__ */ new Set();
|
|
68450
|
+
if (deps.keptMediaKeys !== void 0 && scope.collection === "pipeline-analytics:media") keptMedia = await deps.keptMediaKeys(rows.map((row) => row.key));
|
|
68262
68451
|
for (const [table, ids] of eventIdsByTable) {
|
|
68263
68452
|
if (ids.length === 0) continue;
|
|
68264
68453
|
liveEvents.set(table, await deps.liveEventIds(table, ids));
|
|
@@ -68291,7 +68480,7 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
68291
68480
|
tally.unresolvable += 1;
|
|
68292
68481
|
continue;
|
|
68293
68482
|
}
|
|
68294
|
-
if (ref.kind === "track" ? liveTracks.has(ref.trackId) : ref.kind === "event" ? liveEvents.get(ref.table)?.has(ref.eventId) ?? false : ref.kind === "summary" ? liveSummaries.has(ref.summaryId) : false) continue;
|
|
68483
|
+
if (ref.kind === "track" ? liveTracks.has(ref.trackId) || keptMedia.has(row.key) : ref.kind === "event" ? liveEvents.get(ref.table)?.has(ref.eventId) ?? false : ref.kind === "summary" ? liveSummaries.has(ref.summaryId) : false) continue;
|
|
68295
68484
|
const ts = num(row.data, scope.timestampField);
|
|
68296
68485
|
if (ts !== null && ts > graceFloor) {
|
|
68297
68486
|
tally.withinGrace += 1;
|
|
@@ -78025,10 +78214,149 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
78025
78214
|
const last = track.positions.at(-1)?.bbox;
|
|
78026
78215
|
if (!last) return false;
|
|
78027
78216
|
return bboxMatchesParkedEntry(last, track.className, this.stationaryRegistry?.listViews(track.deviceId) ?? []);
|
|
78028
|
-
}
|
|
78217
|
+
},
|
|
78218
|
+
discardTracks: (tracks) => this.cascadeDiscardedTracks(tracks)
|
|
78029
78219
|
});
|
|
78030
78220
|
return this._trackCloser;
|
|
78031
78221
|
}
|
|
78222
|
+
/**
|
|
78223
|
+
* Cascade what `expireStale` refused to persist (D547): scenery re-fires and
|
|
78224
|
+
* re-sightings of a censused object. Per device, per track, through the
|
|
78225
|
+
* shipped registry — never a raw delete — so the vector, the crops and the
|
|
78226
|
+
* events go with the media. A track whose cascade fails is named.
|
|
78227
|
+
*/
|
|
78228
|
+
async cascadeDiscardedTracks(tracks) {
|
|
78229
|
+
const cascade = this.buildTrackCascadeRegistry();
|
|
78230
|
+
if (!cascade) {
|
|
78231
|
+
this.ctx.logger.warn("discarded tracks could not be cascaded — stores not ready", { meta: { trackIds: tracks.map((t) => t.trackId) } });
|
|
78232
|
+
return;
|
|
78233
|
+
}
|
|
78234
|
+
const byDevice = /* @__PURE__ */ new Map();
|
|
78235
|
+
for (const t of tracks) {
|
|
78236
|
+
const ids = byDevice.get(t.deviceId) ?? [];
|
|
78237
|
+
ids.push(t.trackId);
|
|
78238
|
+
byDevice.set(t.deviceId, ids);
|
|
78239
|
+
}
|
|
78240
|
+
for (const [deviceId, trackIds] of byDevice) {
|
|
78241
|
+
const result = await runTrackCascadeBatch({
|
|
78242
|
+
registry: cascade.registry,
|
|
78243
|
+
trackStore: cascade.trackStore,
|
|
78244
|
+
deviceId,
|
|
78245
|
+
onTrackCleanup: (id, trackId) => this.clearLiveTrackState(id, trackId),
|
|
78246
|
+
onFailure: (trackId, err) => {
|
|
78247
|
+
this.ctx.logger.warn("discarded track could not be cascaded", {
|
|
78248
|
+
tags: { deviceId },
|
|
78249
|
+
meta: {
|
|
78250
|
+
deviceId,
|
|
78251
|
+
trackId,
|
|
78252
|
+
error: String(err)
|
|
78253
|
+
}
|
|
78254
|
+
});
|
|
78255
|
+
}
|
|
78256
|
+
}, trackIds);
|
|
78257
|
+
this.ctx.logger.info("tracks discarded on expiry — still objects are entities, not tracks", {
|
|
78258
|
+
tags: { deviceId },
|
|
78259
|
+
meta: {
|
|
78260
|
+
deviceId,
|
|
78261
|
+
trackIds,
|
|
78262
|
+
deleted: result.deleted,
|
|
78263
|
+
failed: result.failed,
|
|
78264
|
+
mediaRows: result.tally.media
|
|
78265
|
+
}
|
|
78266
|
+
});
|
|
78267
|
+
}
|
|
78268
|
+
}
|
|
78269
|
+
/**
|
|
78270
|
+
* Dispose of the tracks that carried a promoted candidate's evidence (D547):
|
|
78271
|
+
* the one alive on the tipping frame and the persisted source of the entry's
|
|
78272
|
+
* best box. The entry's key frame is spared — it is the one row the entity
|
|
78273
|
+
* vouches for, and `keptStationaryMediaKeys` keeps the orphan audit off it.
|
|
78274
|
+
*/
|
|
78275
|
+
async disposePromotedTracks(deviceId, entry, liveTrackId, sourceTrackId) {
|
|
78276
|
+
const cascade = this.buildTrackCascadeRegistry();
|
|
78277
|
+
const mediaStore = this.mediaStore;
|
|
78278
|
+
if (!cascade || !mediaStore) {
|
|
78279
|
+
this.ctx.logger.warn("stationary promotion: source tracks could not be disposed of — stores not ready", {
|
|
78280
|
+
tags: { deviceId },
|
|
78281
|
+
meta: {
|
|
78282
|
+
deviceId,
|
|
78283
|
+
entryId: entry.id,
|
|
78284
|
+
liveTrackId,
|
|
78285
|
+
sourceTrackId: sourceTrackId ?? null
|
|
78286
|
+
}
|
|
78287
|
+
});
|
|
78288
|
+
return;
|
|
78289
|
+
}
|
|
78290
|
+
const plan = planStationaryTrackDisposal({
|
|
78291
|
+
liveTrackId,
|
|
78292
|
+
...sourceTrackId !== void 0 ? { sourceTrackId } : {},
|
|
78293
|
+
...entry.keyFrameMediaId !== void 0 ? { entryKeyFrameMediaId: entry.keyFrameMediaId } : {}
|
|
78294
|
+
});
|
|
78295
|
+
await disposeStationaryTracks({
|
|
78296
|
+
deviceId,
|
|
78297
|
+
entryId: entry.id,
|
|
78298
|
+
leaves: cascade.registry.filter((leaf) => leaf !== mediaStore),
|
|
78299
|
+
media: mediaStore,
|
|
78300
|
+
trackStore: cascade.trackStore,
|
|
78301
|
+
onTrackCleanup: (id, trackId) => this.clearLiveTrackState(id, trackId),
|
|
78302
|
+
logger: this.ctx.logger
|
|
78303
|
+
}, plan);
|
|
78304
|
+
}
|
|
78305
|
+
/**
|
|
78306
|
+
* Media row keys a live stationary entry vouches for (D547) — the orphan
|
|
78307
|
+
* audit's probe. Answered from the registry's RAM index, which is the
|
|
78308
|
+
* authority on which entries are alive.
|
|
78309
|
+
*/
|
|
78310
|
+
keptStationaryMediaKeys(keys) {
|
|
78311
|
+
const registry = this.stationaryRegistry;
|
|
78312
|
+
if (!registry) return /* @__PURE__ */ new Set();
|
|
78313
|
+
const held = /* @__PURE__ */ new Set();
|
|
78314
|
+
for (const deviceId of registry.deviceIds()) for (const entry of registry.list(deviceId)) if (entry.keyFrameMediaId !== void 0) held.add(String(entry.keyFrameMediaId));
|
|
78315
|
+
return new Set(keys.filter((key) => held.has(key)));
|
|
78316
|
+
}
|
|
78317
|
+
/**
|
|
78318
|
+
* An entry left the registry: free the key frame it vouched for (D547),
|
|
78319
|
+
* unless another live entry still holds it or a track row still owns it
|
|
78320
|
+
* (then retention's cascade will). Never silent either way.
|
|
78321
|
+
*/
|
|
78322
|
+
async releaseStationaryKeyFrame(entry) {
|
|
78323
|
+
const mediaId = entry.keyFrameMediaId;
|
|
78324
|
+
if (mediaId === void 0) return;
|
|
78325
|
+
const deviceId = entry.deviceId;
|
|
78326
|
+
const registry = this.stationaryRegistry;
|
|
78327
|
+
const mediaStore = this.mediaStore;
|
|
78328
|
+
const trackStore = this.trackStore;
|
|
78329
|
+
if (!registry || !mediaStore || !trackStore) return;
|
|
78330
|
+
if (registry.list(deviceId).some((e) => e.keyFrameMediaId === mediaId)) return;
|
|
78331
|
+
try {
|
|
78332
|
+
const owner = await mediaStore.ownerOf(mediaId);
|
|
78333
|
+
if (owner === null) return;
|
|
78334
|
+
if (owner.ownerKind === "track") {
|
|
78335
|
+
if ((await trackStore.existingTrackIds([owner.ownerId])).has(owner.ownerId)) return;
|
|
78336
|
+
}
|
|
78337
|
+
await mediaStore.deleteById(mediaId);
|
|
78338
|
+
this.ctx.logger.info("stationary entry released its key frame", {
|
|
78339
|
+
tags: { deviceId },
|
|
78340
|
+
meta: {
|
|
78341
|
+
deviceId,
|
|
78342
|
+
entryId: entry.id,
|
|
78343
|
+
mediaId,
|
|
78344
|
+
ownerKind: owner.ownerKind,
|
|
78345
|
+
ownerId: owner.ownerId
|
|
78346
|
+
}
|
|
78347
|
+
});
|
|
78348
|
+
} catch (err) {
|
|
78349
|
+
this.ctx.logger.warn("stationary entry key frame could not be released", {
|
|
78350
|
+
tags: { deviceId },
|
|
78351
|
+
meta: {
|
|
78352
|
+
deviceId,
|
|
78353
|
+
entryId: entry.id,
|
|
78354
|
+
mediaId,
|
|
78355
|
+
error: String(err)
|
|
78356
|
+
}
|
|
78357
|
+
});
|
|
78358
|
+
}
|
|
78359
|
+
}
|
|
78032
78360
|
/** Master toggle for the migrated object/face EmbeddingDispatcher. Read at
|
|
78033
78361
|
* init; a change takes effect on addon restart (same convention as
|
|
78034
78362
|
* `postProcessingNodeId`).
|
|
@@ -78754,6 +79082,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
78754
79082
|
store: api.settingsStore,
|
|
78755
79083
|
logger: logger.child("StationaryRegistry"),
|
|
78756
79084
|
ttlForDevice: (deviceId) => this.stationarySettingsFromCache(deviceId).entryTtlMs,
|
|
79085
|
+
onEntryDropped: (entry) => {
|
|
79086
|
+
this.releaseStationaryKeyFrame(entry);
|
|
79087
|
+
},
|
|
78757
79088
|
requestConfirmationLook: (ask) => this.requestConfirmationLook(ask),
|
|
78758
79089
|
onChange: ({ phase, entry, timestamp, observedAt }) => {
|
|
78759
79090
|
this.ctx.eventBus.emit({
|
|
@@ -80142,6 +80473,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
80142
80473
|
anchor: { ...row.anchor },
|
|
80143
80474
|
agreed: row.agreed,
|
|
80144
80475
|
seen: row.seen,
|
|
80476
|
+
undecided: row.undecided,
|
|
80145
80477
|
ratio: row.seen > 0 ? row.agreed / row.seen : 0,
|
|
80146
80478
|
spanMs: row.lastAgreedAt - row.firstAgreedAt,
|
|
80147
80479
|
firstAgreedAt: row.firstAgreedAt,
|
|
@@ -81542,7 +81874,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
81542
81874
|
frameHeight: result.frameHeight,
|
|
81543
81875
|
...observedKeyFrame !== void 0 ? { keyFrameMediaId: observedKeyFrame } : {},
|
|
81544
81876
|
...observedLabel !== void 0 ? { label: observedLabel } : {},
|
|
81545
|
-
...movementState !== void 0 ? { movementState } : {}
|
|
81877
|
+
...movementState !== void 0 ? { movementState } : {},
|
|
81878
|
+
settling: t.settling
|
|
81546
81879
|
};
|
|
81547
81880
|
const outcome = evidence.observe(observation);
|
|
81548
81881
|
if (outcome === void 0 || !outcome.promoted) continue;
|
|
@@ -84692,17 +85025,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
84692
85025
|
wakeAssocIou: gate.wakeAssocIou
|
|
84693
85026
|
});
|
|
84694
85027
|
processor.dropTrack(track.trackId);
|
|
84695
|
-
this.trackStore?.closeActive(track.trackId, "stationary-promotion").catch((err) => {
|
|
84696
|
-
this.ctx.logger.warn("stationary promotion: closing the source track failed — its media, events and vector are now unowned", {
|
|
84697
|
-
tags: { deviceId },
|
|
84698
|
-
meta: {
|
|
84699
|
-
deviceId,
|
|
84700
|
-
trackId: track.trackId,
|
|
84701
|
-
entryId: entry.id,
|
|
84702
|
-
error: String(err)
|
|
84703
|
-
}
|
|
84704
|
-
});
|
|
84705
|
-
});
|
|
84706
85028
|
const faceEnd = this.faceRecognizer?.onTrackEnd(deviceId, track.trackId);
|
|
84707
85029
|
const dropKeyFrame = () => {
|
|
84708
85030
|
this.residents.dropKeyFrameMediaId(track.trackId);
|
|
@@ -84710,7 +85032,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
84710
85032
|
if (faceEnd) faceEnd.finally(dropKeyFrame);
|
|
84711
85033
|
else dropKeyFrame();
|
|
84712
85034
|
const promotedPath = this.trackStore?.getActiveByTrack(track.trackId)?.positions;
|
|
84713
|
-
this.plateRecognizer?.onTrackEnd(deviceId, track.trackId, promotedPath);
|
|
85035
|
+
const plateEnd = this.plateRecognizer?.onTrackEnd(deviceId, track.trackId, promotedPath);
|
|
85036
|
+
Promise.allSettled([faceEnd, plateEnd]).then(() => this.disposePromotedTracks(deviceId, entry, track.trackId, row.sourceTrackId));
|
|
84714
85037
|
this.residents.close(track.trackId, "stationary");
|
|
84715
85038
|
this.detailDispatcher?.onTrackEnded(deviceId, track.trackId);
|
|
84716
85039
|
this.overlayState.onTrackEnded(deviceId, track.trackId);
|
|
@@ -85653,6 +85976,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
85653
85976
|
if (!summaries) throw new Error("summary store not built — probe unavailable");
|
|
85654
85977
|
return summaries.existingIds(ids);
|
|
85655
85978
|
},
|
|
85979
|
+
keptMediaKeys: async (keys) => this.keptStationaryMediaKeys(keys),
|
|
85656
85980
|
reclaim: buildOrphanReclaimer({
|
|
85657
85981
|
deleteFace: async (faceId) => {
|
|
85658
85982
|
await this.faceStore?.delete(faceId);
|