@camstack/addon-post-analysis 1.2.3 → 1.2.5
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-gQ5DHTYd.mjs → dist-Db0CsDGK.mjs} +28 -2
- package/dist/{dist-Ck79MtSp.js → dist-Dlb4YrCt.js} +28 -2
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/{node-C4bKtLou.js → node-DWX3J7jh.js} +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-C4dWm6ZL.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-ACzsl3tP.mjs} +3 -3
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CDgDsFf_.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-3T15bdHA.mjs → hostInit-BdlfdPP1.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +397 -53
- package/dist/pipeline-analytics/index.mjs +396 -52
- 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_ui_mf_2_library__loadShare__.js-CUKChUeW.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-Dlb4YrCt.js");
|
|
6
6
|
let node_crypto = require("node:crypto");
|
|
7
7
|
let sharp = require("sharp");
|
|
8
8
|
sharp = require_dist.__toESM(sharp);
|
|
@@ -3360,6 +3360,24 @@ async function cascadeDeleteTracks(registry, trackStore, trackIds) {
|
|
|
3360
3360
|
for (const trackId of trackIds) await trackStore.deletePersisted(trackId);
|
|
3361
3361
|
}
|
|
3362
3362
|
/**
|
|
3363
|
+
* Full whole-track cascade for ONE track: events → their media + track media →
|
|
3364
|
+
* track row. Delegates to the generalized `cascadeDeleteTracks` so the shipped
|
|
3365
|
+
* `deleteTracks` provider path flows through the same engine while preserving
|
|
3366
|
+
* the exact legacy call order (event ids stay local to the delete). Adapts the
|
|
3367
|
+
* narrow legacy store shapes onto `TrackScopedStore` — a superset store (faces /
|
|
3368
|
+
* embeddings) is opted in by the CALLER's registry, not by this legacy shim.
|
|
3369
|
+
*/
|
|
3370
|
+
async function cascadeDeleteTrack(stores, trackId) {
|
|
3371
|
+
await cascadeDeleteTracks([{ deleteByTracks: async (ids) => {
|
|
3372
|
+
for (const id of ids) {
|
|
3373
|
+
const eventIds = await stores.eventStore.deleteByTrack(id);
|
|
3374
|
+
if (eventIds.length > 0) await stores.mediaStore.deleteForEvents([...eventIds]);
|
|
3375
|
+
}
|
|
3376
|
+
} }, { deleteByTracks: async (ids) => {
|
|
3377
|
+
await stores.mediaStore.deleteForTracks([...ids]);
|
|
3378
|
+
} }], stores.trackStore, [trackId]);
|
|
3379
|
+
}
|
|
3380
|
+
/**
|
|
3363
3381
|
* Widened batch whole-track deletion — the ONE engine behind the three §5 entry
|
|
3364
3382
|
* points. Runs the FULL registry cascade per track (via `cascadeDeleteTracks`
|
|
3365
3383
|
* with a single-id list so the track root is deleted last), fires cleanup on
|
|
@@ -6478,6 +6496,36 @@ function squareSafeCropRegionNormalized(bbox, frame, padding) {
|
|
|
6478
6496
|
};
|
|
6479
6497
|
}
|
|
6480
6498
|
//#endregion
|
|
6499
|
+
//#region src/shared/frame/crop-extractor.ts
|
|
6500
|
+
/**
|
|
6501
|
+
* Extracts a JPEG-encoded crop from a raw frame buffer using a normalized bounding box.
|
|
6502
|
+
* Coordinates are clamped to frame bounds to avoid out-of-range errors.
|
|
6503
|
+
*/
|
|
6504
|
+
async function extractCrop(frameData, frameWidth, frameHeight, bbox) {
|
|
6505
|
+
const rawLeft = Math.round(bbox.x * frameWidth);
|
|
6506
|
+
const rawTop = Math.round(bbox.y * frameHeight);
|
|
6507
|
+
const rawWidth = Math.round(bbox.w * frameWidth);
|
|
6508
|
+
const rawHeight = Math.round(bbox.h * frameHeight);
|
|
6509
|
+
const left = Math.max(0, Math.min(rawLeft, frameWidth - 1));
|
|
6510
|
+
const top = Math.max(0, Math.min(rawTop, frameHeight - 1));
|
|
6511
|
+
const width = Math.max(1, Math.min(rawWidth, frameWidth - left));
|
|
6512
|
+
const height = Math.max(1, Math.min(rawHeight, frameHeight - top));
|
|
6513
|
+
return {
|
|
6514
|
+
crop: await (0, sharp.default)(frameData, { raw: {
|
|
6515
|
+
width: frameWidth,
|
|
6516
|
+
height: frameHeight,
|
|
6517
|
+
channels: 3
|
|
6518
|
+
} }).extract({
|
|
6519
|
+
left,
|
|
6520
|
+
top,
|
|
6521
|
+
width,
|
|
6522
|
+
height
|
|
6523
|
+
}).jpeg({ quality: 90 }).toBuffer(),
|
|
6524
|
+
width,
|
|
6525
|
+
height
|
|
6526
|
+
};
|
|
6527
|
+
}
|
|
6528
|
+
//#endregion
|
|
6481
6529
|
//#region src/shared/frame/square-subject-crop.ts
|
|
6482
6530
|
/** Expansion factor applied to the bbox long side to frame the subject with a
|
|
6483
6531
|
* little breathing room (operator choice: ×1.2). */
|
|
@@ -6716,9 +6764,13 @@ var EventMediaDispatcher = class {
|
|
|
6716
6764
|
const snapshots = input.snapshots ?? [];
|
|
6717
6765
|
const empty = {
|
|
6718
6766
|
storedSnapshots: [],
|
|
6719
|
-
thumbnailTrackIds: []
|
|
6767
|
+
thumbnailTrackIds: [],
|
|
6768
|
+
firstFrameTrackIds: [],
|
|
6769
|
+
lastFrameTrackIds: [],
|
|
6770
|
+
rasterFallbacks: []
|
|
6720
6771
|
};
|
|
6721
|
-
|
|
6772
|
+
const extraCandidateCount = input.rasterFallbackCandidates?.length ?? 0;
|
|
6773
|
+
if (events.length === 0 && trackFrames.length === 0 && snapshots.length === 0 && extraCandidateCount === 0) return empty;
|
|
6722
6774
|
let decoded;
|
|
6723
6775
|
try {
|
|
6724
6776
|
decoded = await resolveFrame(frameHandle, { getRemoteFrame: this.deps.getRemoteFrame });
|
|
@@ -6768,21 +6820,82 @@ var EventMediaDispatcher = class {
|
|
|
6768
6820
|
});
|
|
6769
6821
|
return empty;
|
|
6770
6822
|
}
|
|
6823
|
+
const rasterFallbacks = await this.collectRasterFallbacks(frameData, fw, fh, trackFrames, snapshots, input.cropPadding, input.rasterFallbackWantedTrackIds, input.rasterFallbackCandidates);
|
|
6771
6824
|
for (const ev of events) await this.writeEventMedia(deviceId, frameHandle, fw, fh, ev, input.cropPadding);
|
|
6772
|
-
|
|
6825
|
+
const firstFrameTrackIds = [];
|
|
6826
|
+
for (const tf of trackFrames) if (await this.writeTrackFrame(deviceId, frameHandle, frameData, fw, fh, tf)) firstFrameTrackIds.push(tf.trackId);
|
|
6773
6827
|
const storedSnapshots = [];
|
|
6774
6828
|
const thumbnailTrackIds = [];
|
|
6829
|
+
const lastFrameTrackIds = [];
|
|
6775
6830
|
for (const sn of snapshots) {
|
|
6776
6831
|
const res = await this.writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn);
|
|
6777
6832
|
if (res.storedSnapshot) storedSnapshots.push(res.storedSnapshot);
|
|
6778
6833
|
if (res.thumbnailWritten) thumbnailTrackIds.push(sn.trackId);
|
|
6834
|
+
if (res.lastFrameWritten) lastFrameTrackIds.push(sn.trackId);
|
|
6779
6835
|
}
|
|
6780
6836
|
return {
|
|
6781
6837
|
storedSnapshots,
|
|
6782
|
-
thumbnailTrackIds
|
|
6838
|
+
thumbnailTrackIds,
|
|
6839
|
+
firstFrameTrackIds,
|
|
6840
|
+
lastFrameTrackIds,
|
|
6841
|
+
rasterFallbacks
|
|
6783
6842
|
};
|
|
6784
6843
|
}
|
|
6785
6844
|
/**
|
|
6845
|
+
* Cut ONE clean detection-raster subject crop per WANTED track observed this
|
|
6846
|
+
* frame — the "first available detection-raster frame" of the zero-media
|
|
6847
|
+
* fallback. Candidate bboxes come from this frame's firstFrame/snapshot targets
|
|
6848
|
+
* AND (widened) the explicit `extraCandidates` (confirmed tracks with no such
|
|
6849
|
+
* target). Cropped from the already-resolved `frameData` at its REAL resolution
|
|
6850
|
+
* via {@link extractCrop} (extract-only — NEVER upscaled). Deduped per trackId
|
|
6851
|
+
* (first candidate wins; target-derived candidates precede the extras). A
|
|
6852
|
+
* per-track encode failure is skipped (logged) — a missing fallback simply
|
|
6853
|
+
* leaves the track with no last-resort preview, never an error.
|
|
6854
|
+
*/
|
|
6855
|
+
async collectRasterFallbacks(frameData, fw, fh, trackFrames, snapshots, cropPadding, wanted, extraCandidates) {
|
|
6856
|
+
if (!wanted || wanted.size === 0) return [];
|
|
6857
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6858
|
+
const out = [];
|
|
6859
|
+
const candidates = [
|
|
6860
|
+
...trackFrames.map((t) => ({
|
|
6861
|
+
trackId: t.trackId,
|
|
6862
|
+
timestamp: t.timestamp,
|
|
6863
|
+
bbox: t.bbox
|
|
6864
|
+
})),
|
|
6865
|
+
...snapshots.map((s) => ({
|
|
6866
|
+
trackId: s.trackId,
|
|
6867
|
+
timestamp: s.timestamp,
|
|
6868
|
+
bbox: s.bbox
|
|
6869
|
+
})),
|
|
6870
|
+
...(extraCandidates ?? []).map((c) => ({
|
|
6871
|
+
trackId: c.trackId,
|
|
6872
|
+
timestamp: c.timestamp,
|
|
6873
|
+
bbox: c.bbox
|
|
6874
|
+
}))
|
|
6875
|
+
];
|
|
6876
|
+
for (const c of candidates) {
|
|
6877
|
+
if (!wanted.has(c.trackId) || seen.has(c.trackId)) continue;
|
|
6878
|
+
seen.add(c.trackId);
|
|
6879
|
+
try {
|
|
6880
|
+
const { crop } = await extractCrop(frameData, fw, fh, squareSafeCropRegionNormalized(c.bbox, {
|
|
6881
|
+
W: fw,
|
|
6882
|
+
H: fh
|
|
6883
|
+
}, cropPadding));
|
|
6884
|
+
out.push({
|
|
6885
|
+
trackId: c.trackId,
|
|
6886
|
+
timestamp: c.timestamp,
|
|
6887
|
+
jpeg: crop
|
|
6888
|
+
});
|
|
6889
|
+
} catch (err) {
|
|
6890
|
+
this.deps.logger.debug("event media: raster fallback crop failed", { meta: {
|
|
6891
|
+
trackId: c.trackId,
|
|
6892
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6893
|
+
} });
|
|
6894
|
+
}
|
|
6895
|
+
}
|
|
6896
|
+
return out;
|
|
6897
|
+
}
|
|
6898
|
+
/**
|
|
6786
6899
|
* Periodic per-track media (§5). The boxed FULL frame is encoded once and
|
|
6787
6900
|
* shared by the appended `snapshot` (timeline filmstrip) and the rolling
|
|
6788
6901
|
* `lastFrame` (overwrite). The best `thumbnail` is DIFFERENT: a clean
|
|
@@ -6793,11 +6906,15 @@ var EventMediaDispatcher = class {
|
|
|
6793
6906
|
* TrackStore wiring (null when `appendSnapshot` is false or the encode
|
|
6794
6907
|
* failed). `thumbnailWritten` reports whether a best `thumbnail` actually
|
|
6795
6908
|
* landed this frame (#27-A) so the caller can stop forcing retries.
|
|
6909
|
+
* `lastFrameWritten` reports whether the rolling `lastFrame` actually landed
|
|
6910
|
+
* (DEFECT B) so the caller advances its `lastFrameAt` clock ONLY on a real
|
|
6911
|
+
* write — a dropped roll leaves the clock put and retries next frame.
|
|
6796
6912
|
*/
|
|
6797
6913
|
async writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn) {
|
|
6798
6914
|
if (!sn.appendSnapshot && !sn.rollingLastFrame && !sn.bestThumbnail) return {
|
|
6799
6915
|
storedSnapshot: null,
|
|
6800
|
-
thumbnailWritten: false
|
|
6916
|
+
thumbnailWritten: false,
|
|
6917
|
+
lastFrameWritten: false
|
|
6801
6918
|
};
|
|
6802
6919
|
let boxed = null;
|
|
6803
6920
|
if (sn.appendSnapshot || sn.rollingLastFrame) boxed = await this.boxedTimelineFrame(deviceId, frameHandle, frameData, fw, fh, sn.trackId, sn.bbox, sn.label);
|
|
@@ -6818,7 +6935,8 @@ var EventMediaDispatcher = class {
|
|
|
6818
6935
|
bbox: sn.bbox
|
|
6819
6936
|
};
|
|
6820
6937
|
} catch {}
|
|
6821
|
-
|
|
6938
|
+
let lastFrameWritten = false;
|
|
6939
|
+
if (sn.rollingLastFrame && boxed) lastFrameWritten = await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
|
|
6822
6940
|
let thumbnailWritten = false;
|
|
6823
6941
|
if (sn.bestThumbnail) {
|
|
6824
6942
|
const variants = await this.cropSubjectVariants(frameHandle, fw, fh, sn.bbox);
|
|
@@ -6829,7 +6947,8 @@ var EventMediaDispatcher = class {
|
|
|
6829
6947
|
}
|
|
6830
6948
|
return {
|
|
6831
6949
|
storedSnapshot: stored,
|
|
6832
|
-
thumbnailWritten
|
|
6950
|
+
thumbnailWritten,
|
|
6951
|
+
lastFrameWritten
|
|
6833
6952
|
};
|
|
6834
6953
|
}
|
|
6835
6954
|
/**
|
|
@@ -7026,7 +7145,7 @@ var EventMediaDispatcher = class {
|
|
|
7026
7145
|
}
|
|
7027
7146
|
async writeTrackFrame(deviceId, frameHandle, frameData, fw, fh, tf) {
|
|
7028
7147
|
const boxed = await this.boxedTimelineFrame(deviceId, frameHandle, frameData, fw, fh, tf.trackId, tf.bbox, tf.label);
|
|
7029
|
-
if (!boxed) return;
|
|
7148
|
+
if (!boxed) return false;
|
|
7030
7149
|
try {
|
|
7031
7150
|
await this.deps.mediaStore.put({
|
|
7032
7151
|
deviceId,
|
|
@@ -7036,6 +7155,7 @@ var EventMediaDispatcher = class {
|
|
|
7036
7155
|
timestamp: tf.timestamp,
|
|
7037
7156
|
data: boxed
|
|
7038
7157
|
});
|
|
7158
|
+
return true;
|
|
7039
7159
|
} catch (err) {
|
|
7040
7160
|
this.deps.logger.warn("event media: track frame failed", {
|
|
7041
7161
|
tags: { deviceId },
|
|
@@ -7045,40 +7165,11 @@ var EventMediaDispatcher = class {
|
|
|
7045
7165
|
error: err instanceof Error ? err.message : String(err)
|
|
7046
7166
|
}
|
|
7047
7167
|
});
|
|
7168
|
+
return false;
|
|
7048
7169
|
}
|
|
7049
7170
|
}
|
|
7050
7171
|
};
|
|
7051
7172
|
//#endregion
|
|
7052
|
-
//#region src/shared/frame/crop-extractor.ts
|
|
7053
|
-
/**
|
|
7054
|
-
* Extracts a JPEG-encoded crop from a raw frame buffer using a normalized bounding box.
|
|
7055
|
-
* Coordinates are clamped to frame bounds to avoid out-of-range errors.
|
|
7056
|
-
*/
|
|
7057
|
-
async function extractCrop(frameData, frameWidth, frameHeight, bbox) {
|
|
7058
|
-
const rawLeft = Math.round(bbox.x * frameWidth);
|
|
7059
|
-
const rawTop = Math.round(bbox.y * frameHeight);
|
|
7060
|
-
const rawWidth = Math.round(bbox.w * frameWidth);
|
|
7061
|
-
const rawHeight = Math.round(bbox.h * frameHeight);
|
|
7062
|
-
const left = Math.max(0, Math.min(rawLeft, frameWidth - 1));
|
|
7063
|
-
const top = Math.max(0, Math.min(rawTop, frameHeight - 1));
|
|
7064
|
-
const width = Math.max(1, Math.min(rawWidth, frameWidth - left));
|
|
7065
|
-
const height = Math.max(1, Math.min(rawHeight, frameHeight - top));
|
|
7066
|
-
return {
|
|
7067
|
-
crop: await (0, sharp.default)(frameData, { raw: {
|
|
7068
|
-
width: frameWidth,
|
|
7069
|
-
height: frameHeight,
|
|
7070
|
-
channels: 3
|
|
7071
|
-
} }).extract({
|
|
7072
|
-
left,
|
|
7073
|
-
top,
|
|
7074
|
-
width,
|
|
7075
|
-
height
|
|
7076
|
-
}).jpeg({ quality: 90 }).toBuffer(),
|
|
7077
|
-
width,
|
|
7078
|
-
height
|
|
7079
|
-
};
|
|
7080
|
-
}
|
|
7081
|
-
//#endregion
|
|
7082
7173
|
//#region src/pipeline-analytics/embedding/embedding-dispatcher.ts
|
|
7083
7174
|
function selectCropBbox(detection) {
|
|
7084
7175
|
return detection.refinedBbox ?? detection.bbox;
|
|
@@ -8867,6 +8958,16 @@ function planPeriodicMedia(input) {
|
|
|
8867
8958
|
bestThumbnail: input.isNewBest || !thumbnailLanded
|
|
8868
8959
|
};
|
|
8869
8960
|
}
|
|
8961
|
+
var DEFAULT_ZERO_MEDIA_POLICY_CONFIG = { suppressMaxDurationMs: 1e3 };
|
|
8962
|
+
/**
|
|
8963
|
+
* Classify a closing track's persistence outcome. Pure — see the module header
|
|
8964
|
+
* for the full contract.
|
|
8965
|
+
*/
|
|
8966
|
+
function decideZeroMediaPolicy(input, config = DEFAULT_ZERO_MEDIA_POLICY_CONFIG) {
|
|
8967
|
+
if (input.hasMedia) return "persist";
|
|
8968
|
+
if (input.durationMs < config.suppressMaxDurationMs && !input.confirmed) return "suppress";
|
|
8969
|
+
return input.hasRasterFallback ? "raster-fallback" : "persist";
|
|
8970
|
+
}
|
|
8870
8971
|
//#endregion
|
|
8871
8972
|
//#region src/pipeline-analytics/best-thumbnail-guard.ts
|
|
8872
8973
|
/**
|
|
@@ -12126,6 +12227,23 @@ function pickTrackFallbackMedia(files) {
|
|
|
12126
12227
|
return files.find((f) => f.kind === "thumbnailSmall") ?? files.find((f) => f.kind === "thumbnail") ?? files.find((f) => f.kind === "lastFrame") ?? files.find((f) => f.kind === "firstFrame") ?? [...files].reverse().find((f) => f.kind === "snapshot") ?? files[files.length - 1];
|
|
12127
12228
|
}
|
|
12128
12229
|
/**
|
|
12230
|
+
* Crop-forced resolution (the `?kind=crop` reel/timeline path). Prefer a CLEAN
|
|
12231
|
+
* (never-boxed) frame via the injected `pickClean`; when the track owns NO clean
|
|
12232
|
+
* frame at all — the regression class created by retiring `crop`/`fullFrame`
|
|
12233
|
+
* production (2026-07-21), leaving degenerate tracks with only boxed
|
|
12234
|
+
* `lastFrame`/`snapshot`/`firstFrame` — degrade to ANY track media rather than
|
|
12235
|
+
* returning undefined. A boxed frame (with the target's box drawn) is a real
|
|
12236
|
+
* preview; an empty tile is not. Clean is always tried FIRST so the common case
|
|
12237
|
+
* still serves an unboxed tile. Undefined only when the owner has NO media.
|
|
12238
|
+
*
|
|
12239
|
+
* `pickClean` is injected because the clean-kind picker (`pickCleanMedia`) lives
|
|
12240
|
+
* in the addon `index.ts` over the concrete `MediaFileKind` set; this keeps the
|
|
12241
|
+
* composition pure + unit-testable here.
|
|
12242
|
+
*/
|
|
12243
|
+
function resolveCropForcedMedia(files, pickClean) {
|
|
12244
|
+
return pickClean(files) ?? pickTrackFallbackMedia(files);
|
|
12245
|
+
}
|
|
12246
|
+
/**
|
|
12129
12247
|
* Resolve the default-path media for an event id: the event's own media when it
|
|
12130
12248
|
* has any, else the owning track's fallback media. Returns `null` when neither
|
|
12131
12249
|
* yields a blob (caller 404s → the surface shows an icon).
|
|
@@ -12457,6 +12575,32 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12457
12575
|
* where no `snapshot` is appended, so it is never byte-identical to a stored
|
|
12458
12576
|
* `snapshot` (kills the end-of-track duplicate). Cleared on track end. */
|
|
12459
12577
|
lastFrameAtByTrack = /* @__PURE__ */ new Map();
|
|
12578
|
+
/** Tracks with a rolling-`lastFrame` capture CURRENTLY in flight (RC-1,
|
|
12579
|
+
* DEFECT B). The `lastFrameAt` clock now advances ONLY when the write lands
|
|
12580
|
+
* (dispatcher completion), not synchronously at plan time — so a dropped roll
|
|
12581
|
+
* (recycled/blank frame) leaves the clock put and re-rolls next frame. Without
|
|
12582
|
+
* an in-flight guard that re-roll would fire EVERY frame while the first
|
|
12583
|
+
* capture is still resolving (0.1–3s under the native path), stacking N
|
|
12584
|
+
* overlapping captures. While a track sits here `buildSnapshotTargets`
|
|
12585
|
+
* suppresses a new rolling-`lastFrame` request; the pending dispatch clears it
|
|
12586
|
+
* (and, if it landed, advances `lastFrameAtByTrack`). Cleared on track end +
|
|
12587
|
+
* reset. */
|
|
12588
|
+
lastFrameInFlight = /* @__PURE__ */ new Set();
|
|
12589
|
+
/** Confirmed-birth tracks whose `firstFrame` has NOT yet actually persisted
|
|
12590
|
+
* (DEFECT A). Seeded on a confirmed birth (alongside the birth firstFrame
|
|
12591
|
+
* target) and removed the moment the write lands. While a track sits here and
|
|
12592
|
+
* is matched this frame, `collectFirstFrameRetries` re-schedules a firstFrame
|
|
12593
|
+
* target so a birth capture dropped by a recycled/blank live frame is retried
|
|
12594
|
+
* on a later frame (earliest available view still beats none). Scoped to
|
|
12595
|
+
* confirmed births ONLY — a suppressed false-positive birth or resurrection
|
|
12596
|
+
* never enters, so it never gets a retro firstFrame. Cleared on track end +
|
|
12597
|
+
* reset. */
|
|
12598
|
+
firstFramePendingTracks = /* @__PURE__ */ new Set();
|
|
12599
|
+
/** Tracks with a `firstFrame` capture CURRENTLY in flight (RC-1, DEFECT A).
|
|
12600
|
+
* Mirrors `thumbnailInFlight`: while a firstFrame capture is resolving the
|
|
12601
|
+
* per-frame retry is suppressed so at most one capture is outstanding per
|
|
12602
|
+
* track. Cleared on dispatch settle (+ track end / reset). */
|
|
12603
|
+
firstFrameInFlight = /* @__PURE__ */ new Set();
|
|
12460
12604
|
/** Track ids whose best `thumbnail` has ACTUALLY been persisted (#27-A). A
|
|
12461
12605
|
* track absent here keeps forcing a best-thumbnail capture every frame until
|
|
12462
12606
|
* one lands, so a short / high-churn track whose first capture was dropped
|
|
@@ -12481,6 +12625,14 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12481
12625
|
* area) + emit time, so a material improvement is measured against the
|
|
12482
12626
|
* last emit and debounced. Seeded at `start`, dropped at `end`. */
|
|
12483
12627
|
trackLifecycleUpdateMem = /* @__PURE__ */ new Map();
|
|
12628
|
+
/** Per-active-track close-policy state (spec §"Zero-media track policy"):
|
|
12629
|
+
* whether the track ever reached the tracker's hit-confirmation
|
|
12630
|
+
* (`trackAge >= minHits`) and the FIRST retained detection-raster subject
|
|
12631
|
+
* crop. Both are read ONLY at close (`sweepExpiredTracks`) to decide
|
|
12632
|
+
* suppress / raster-fallback / persist for a track that ends with no media.
|
|
12633
|
+
* `deviceId` lets `clearDevice` prune without an active-track lookup. Dropped
|
|
12634
|
+
* on every teardown path. */
|
|
12635
|
+
trackClosureState = /* @__PURE__ */ new Map();
|
|
12484
12636
|
/** The shared crop extractor (native-res first, detection-frame fallback),
|
|
12485
12637
|
* captured in the constructor so `processFrame` can crop object thumbnails in
|
|
12486
12638
|
* the same live-frame window as the face/plate/event-media captures. The
|
|
@@ -12539,7 +12691,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12539
12691
|
let storage = this.ctx.kernel.storage;
|
|
12540
12692
|
const mediaRoot = process.env.CAMSTACK_MEDIA_ROOT?.trim();
|
|
12541
12693
|
if (mediaRoot) {
|
|
12542
|
-
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-
|
|
12694
|
+
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-DWX3J7jh.js"));
|
|
12543
12695
|
storage = new FilesystemStorageProvider(mediaRoot);
|
|
12544
12696
|
logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
|
|
12545
12697
|
}
|
|
@@ -12972,6 +13124,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12972
13124
|
this.bindingCache?.onBindingsChanged(data);
|
|
12973
13125
|
if (data.capName === "pipeline-analytics" && data.reason === "wrapper-deactivated") {
|
|
12974
13126
|
this.trackStore?.clearDevice(data.deviceId);
|
|
13127
|
+
this.clearClosureStateForDevice(data.deviceId);
|
|
12975
13128
|
this.stationaryRegistry?.forgetDevice(data.deviceId);
|
|
12976
13129
|
this.overlayState.clearDevice(data.deviceId);
|
|
12977
13130
|
this.overlaySynthesisWarnAt.delete(data.deviceId);
|
|
@@ -12990,6 +13143,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12990
13143
|
this.unsubDeviceUnreg = this.ctx.eventBus.subscribe({ category: require_dist.EventCategory.DeviceUnregistered }, (ev) => {
|
|
12991
13144
|
const { deviceId } = ev.data;
|
|
12992
13145
|
this.trackStore?.clearDevice(deviceId);
|
|
13146
|
+
this.clearClosureStateForDevice(deviceId);
|
|
12993
13147
|
this.stationaryRegistry?.forgetDevice(deviceId);
|
|
12994
13148
|
this.overlayState.clearDevice(deviceId);
|
|
12995
13149
|
this.overlaySynthesisWarnAt.delete(deviceId);
|
|
@@ -13341,10 +13495,14 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
13341
13495
|
this.dropoutSkipsByKey.clear();
|
|
13342
13496
|
this.bestFrameTracker.clear();
|
|
13343
13497
|
this.lastFrameAtByTrack.clear();
|
|
13498
|
+
this.lastFrameInFlight.clear();
|
|
13499
|
+
this.firstFramePendingTracks.clear();
|
|
13500
|
+
this.firstFrameInFlight.clear();
|
|
13344
13501
|
this.thumbnailLandedTracks.clear();
|
|
13345
13502
|
this.thumbnailInFlight.clear();
|
|
13346
13503
|
this.keyFrameInFlight.clear();
|
|
13347
13504
|
this.trackLifecycleUpdateMem.clear();
|
|
13505
|
+
this.trackClosureState.clear();
|
|
13348
13506
|
this.objectEmbeddingBestSelector.clear();
|
|
13349
13507
|
this.levelStateByDevice.clear();
|
|
13350
13508
|
this.settingsCacheByDevice.clear();
|
|
@@ -13463,6 +13621,10 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
13463
13621
|
});
|
|
13464
13622
|
positionsCountById.set(t.trackId, upserted.positions.length);
|
|
13465
13623
|
}
|
|
13624
|
+
if (result.rawTrackedDetections.length > 0) {
|
|
13625
|
+
const { minHits } = await this.resolveDeviceDetectionSensitivitySettings(deviceId);
|
|
13626
|
+
for (const rt of result.rawTrackedDetections) if (rt.trackAge >= minHits) this.markTrackConfirmed(deviceId, rt.trackId);
|
|
13627
|
+
}
|
|
13466
13628
|
const log = this.ctx.logger.withTags({ deviceId });
|
|
13467
13629
|
const prevIds = this.lastActiveTrackIds.get(key) ?? /* @__PURE__ */ new Set();
|
|
13468
13630
|
const firstFrameTargets = [];
|
|
@@ -13507,14 +13669,16 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
13507
13669
|
className: t.className,
|
|
13508
13670
|
source
|
|
13509
13671
|
} });
|
|
13510
|
-
if (this.eventMediaDispatcher
|
|
13511
|
-
|
|
13672
|
+
if (this.eventMediaDispatcher) {
|
|
13673
|
+
this.firstFramePendingTracks.add(id);
|
|
13674
|
+
this.trackStore.seedSnapshotClock(id, result.timestamp, t.bbox);
|
|
13675
|
+
this.lastFrameAtByTrack.set(id, result.timestamp);
|
|
13676
|
+
if (frameHandle) firstFrameTargets.push({
|
|
13512
13677
|
trackId: id,
|
|
13513
13678
|
timestamp: result.timestamp,
|
|
13514
13679
|
bbox: { ...t.bbox },
|
|
13515
13680
|
...t.label ? { label: t.label } : {}
|
|
13516
13681
|
});
|
|
13517
|
-
this.trackStore.seedSnapshotClock(id, result.timestamp, t.bbox);
|
|
13518
13682
|
}
|
|
13519
13683
|
this.ctx.eventBus.emit({
|
|
13520
13684
|
id: `pa-${(0, node_crypto.randomUUID)()}`,
|
|
@@ -13663,6 +13827,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
13663
13827
|
for (const crops of childCropsByEvent.values()) for (const c of crops) if (c.kind === "faceCrop") faceCrops += 1;
|
|
13664
13828
|
else plateCrops += 1;
|
|
13665
13829
|
const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight);
|
|
13830
|
+
for (const retry of this.collectFirstFrameRetries(result.tracked, result.timestamp, firstFrameTargets)) firstFrameTargets.push(retry);
|
|
13666
13831
|
const keyFrameTrackIds = selectKeyFrameTrackIds(snapshotTargets);
|
|
13667
13832
|
if (keyFrameTrackIds.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameTrackIds, frameHandle);
|
|
13668
13833
|
if (eventTargets.length > 0 || firstFrameTargets.length > 0 || snapshotTargets.length > 0) {
|
|
@@ -13686,14 +13851,46 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
13686
13851
|
} });
|
|
13687
13852
|
const thumbInFlightTrackIds = snapshotTargets.filter((t) => t.bestThumbnail).map((t) => t.trackId);
|
|
13688
13853
|
for (const trackId of thumbInFlightTrackIds) this.thumbnailInFlight.add(trackId);
|
|
13854
|
+
const firstFrameInFlightTrackIds = firstFrameTargets.map((t) => t.trackId);
|
|
13855
|
+
for (const trackId of firstFrameInFlightTrackIds) this.firstFrameInFlight.add(trackId);
|
|
13856
|
+
const lastFrameInFlightTrackIds = snapshotTargets.filter((t) => t.rollingLastFrame).map((t) => t.trackId);
|
|
13857
|
+
for (const trackId of lastFrameInFlightTrackIds) this.lastFrameInFlight.add(trackId);
|
|
13858
|
+
const dispatchTimestamp = result.timestamp;
|
|
13859
|
+
const rasterFallbackWantedTrackIds = /* @__PURE__ */ new Set();
|
|
13860
|
+
for (const t of firstFrameTargets) if (!this.trackClosureState.get(t.trackId)?.rasterFallback) rasterFallbackWantedTrackIds.add(t.trackId);
|
|
13861
|
+
for (const s of snapshotTargets) if (!this.trackClosureState.get(s.trackId)?.rasterFallback) rasterFallbackWantedTrackIds.add(s.trackId);
|
|
13862
|
+
const targetedThisFrame = new Set([...firstFrameTargets.map((t) => t.trackId), ...snapshotTargets.map((s) => s.trackId)]);
|
|
13863
|
+
const rasterFallbackCandidates = [];
|
|
13864
|
+
for (const t of result.tracked) {
|
|
13865
|
+
if (t.matchedThisFrame === false) continue;
|
|
13866
|
+
const closure = this.trackClosureState.get(t.trackId);
|
|
13867
|
+
if (!closure?.confirmed) continue;
|
|
13868
|
+
if (closure.rasterFallback) continue;
|
|
13869
|
+
if (targetedThisFrame.has(t.trackId)) continue;
|
|
13870
|
+
rasterFallbackWantedTrackIds.add(t.trackId);
|
|
13871
|
+
rasterFallbackCandidates.push({
|
|
13872
|
+
trackId: t.trackId,
|
|
13873
|
+
timestamp: result.timestamp,
|
|
13874
|
+
bbox: { ...t.bbox }
|
|
13875
|
+
});
|
|
13876
|
+
}
|
|
13689
13877
|
this.eventMediaDispatcher.captureForFrame({
|
|
13690
13878
|
deviceId,
|
|
13691
13879
|
frameHandle,
|
|
13692
13880
|
events: eventTargets,
|
|
13693
13881
|
trackFrames: firstFrameTargets,
|
|
13694
13882
|
snapshots: snapshotTargets,
|
|
13695
|
-
cropPadding: mediaSettings.cropPadding
|
|
13883
|
+
cropPadding: mediaSettings.cropPadding,
|
|
13884
|
+
rasterFallbackWantedTrackIds,
|
|
13885
|
+
rasterFallbackCandidates
|
|
13696
13886
|
}).then((res) => {
|
|
13887
|
+
for (const rf of res.rasterFallbacks) {
|
|
13888
|
+
const st = this.ensureTrackClosureState(deviceId, rf.trackId);
|
|
13889
|
+
if (!st.rasterFallback) st.rasterFallback = {
|
|
13890
|
+
jpeg: rf.jpeg,
|
|
13891
|
+
timestamp: rf.timestamp
|
|
13892
|
+
};
|
|
13893
|
+
}
|
|
13697
13894
|
for (const s of res.storedSnapshots) this.trackStore?.addSnapshot(s.trackId, {
|
|
13698
13895
|
timestamp: s.timestamp,
|
|
13699
13896
|
position: {
|
|
@@ -13705,8 +13902,12 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
13705
13902
|
mediaKey: s.mediaKey
|
|
13706
13903
|
});
|
|
13707
13904
|
for (const trackId of res.thumbnailTrackIds) this.thumbnailLandedTracks.add(trackId);
|
|
13905
|
+
for (const trackId of res.firstFrameTrackIds) this.firstFramePendingTracks.delete(trackId);
|
|
13906
|
+
for (const trackId of res.lastFrameTrackIds) this.lastFrameAtByTrack.set(trackId, dispatchTimestamp);
|
|
13708
13907
|
}).catch(() => {}).finally(() => {
|
|
13709
13908
|
for (const trackId of thumbInFlightTrackIds) this.thumbnailInFlight.delete(trackId);
|
|
13909
|
+
for (const trackId of firstFrameInFlightTrackIds) this.firstFrameInFlight.delete(trackId);
|
|
13910
|
+
for (const trackId of lastFrameInFlightTrackIds) this.lastFrameInFlight.delete(trackId);
|
|
13710
13911
|
});
|
|
13711
13912
|
}
|
|
13712
13913
|
}
|
|
@@ -13770,6 +13971,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
13770
13971
|
}
|
|
13771
13972
|
overlayDetections = frame.detections;
|
|
13772
13973
|
}
|
|
13974
|
+
const hasMovingTrack = result.tracked.some((t) => t.state === "moving" || t.state === "entered" || t.state === "left");
|
|
13773
13975
|
this.ctx.eventBus.emit({
|
|
13774
13976
|
id: `pa-${(0, node_crypto.randomUUID)()}`,
|
|
13775
13977
|
timestamp: new Date(result.timestamp),
|
|
@@ -13784,7 +13986,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
13784
13986
|
timestamp: result.timestamp,
|
|
13785
13987
|
frameWidth: result.frameWidth,
|
|
13786
13988
|
frameHeight: result.frameHeight,
|
|
13787
|
-
detections: overlayDetections
|
|
13989
|
+
detections: overlayDetections,
|
|
13990
|
+
hasMovingTrack
|
|
13788
13991
|
}
|
|
13789
13992
|
});
|
|
13790
13993
|
}
|
|
@@ -14408,6 +14611,34 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
14408
14611
|
});
|
|
14409
14612
|
this.emitTrackLifecycle(payload, timestamp);
|
|
14410
14613
|
}
|
|
14614
|
+
/**
|
|
14615
|
+
* DEFECT A: build `firstFrame` RETRY targets for confirmed-birth tracks whose
|
|
14616
|
+
* birth capture never landed. A track qualifies when it is still pending
|
|
14617
|
+
* (`firstFramePendingTracks`), is OBSERVED this frame (`matchedThisFrame` — a
|
|
14618
|
+
* coasted/stale box would crop the empty scene), is NOT already targeted this
|
|
14619
|
+
* frame (its birth target), and has no capture in flight (RC-1). Each target is
|
|
14620
|
+
* stamped with the CURRENT frame timestamp so the media carries the real
|
|
14621
|
+
* capture instant, not the birth ts — the earliest AVAILABLE view still beats
|
|
14622
|
+
* no firstFrame at all.
|
|
14623
|
+
*/
|
|
14624
|
+
collectFirstFrameRetries(tracked, timestamp, alreadyTargeted) {
|
|
14625
|
+
if (this.firstFramePendingTracks.size === 0) return [];
|
|
14626
|
+
const bornThisFrame = new Set(alreadyTargeted.map((t) => t.trackId));
|
|
14627
|
+
const retries = [];
|
|
14628
|
+
for (const t of tracked) {
|
|
14629
|
+
if (t.matchedThisFrame === false) continue;
|
|
14630
|
+
if (!this.firstFramePendingTracks.has(t.trackId)) continue;
|
|
14631
|
+
if (bornThisFrame.has(t.trackId)) continue;
|
|
14632
|
+
if (this.firstFrameInFlight.has(t.trackId)) continue;
|
|
14633
|
+
retries.push({
|
|
14634
|
+
trackId: t.trackId,
|
|
14635
|
+
timestamp,
|
|
14636
|
+
bbox: { ...t.bbox },
|
|
14637
|
+
...t.label ? { label: t.label } : {}
|
|
14638
|
+
});
|
|
14639
|
+
}
|
|
14640
|
+
return retries;
|
|
14641
|
+
}
|
|
14411
14642
|
buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight) {
|
|
14412
14643
|
const targets = [];
|
|
14413
14644
|
for (const t of tracked) {
|
|
@@ -14441,19 +14672,19 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
14441
14672
|
now: timestamp,
|
|
14442
14673
|
intervalMs: media.snapshotIntervalMs
|
|
14443
14674
|
});
|
|
14444
|
-
|
|
14675
|
+
const rollingLastFrame = plan.rollingLastFrame && !this.lastFrameInFlight.has(t.trackId);
|
|
14445
14676
|
if (plan.appendSnapshot) this.trackStore.markSnapshotPending(t.trackId, timestamp, t.bbox);
|
|
14446
14677
|
const plausibleBox = isPlausibleThumbnailBox(t.bbox, frameWidth, frameHeight);
|
|
14447
14678
|
const bestThumbnail = plan.bestThumbnail && plausibleBox && !this.thumbnailInFlight.has(t.trackId);
|
|
14448
14679
|
const keyFrame = isNewBest && plausibleBox;
|
|
14449
|
-
if (!plan.appendSnapshot && !
|
|
14680
|
+
if (!plan.appendSnapshot && !rollingLastFrame && !bestThumbnail && !keyFrame) continue;
|
|
14450
14681
|
targets.push({
|
|
14451
14682
|
trackId: t.trackId,
|
|
14452
14683
|
timestamp,
|
|
14453
14684
|
bbox: { ...t.bbox },
|
|
14454
14685
|
...t.label ? { label: t.label } : {},
|
|
14455
14686
|
appendSnapshot: plan.appendSnapshot,
|
|
14456
|
-
rollingLastFrame
|
|
14687
|
+
rollingLastFrame,
|
|
14457
14688
|
bestThumbnail,
|
|
14458
14689
|
keyFrame
|
|
14459
14690
|
});
|
|
@@ -14704,6 +14935,44 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
14704
14935
|
const expired = await this.trackStore.expireStale(Date.now());
|
|
14705
14936
|
for (const t of expired) {
|
|
14706
14937
|
const duration = t.lastSeen - t.firstSeen;
|
|
14938
|
+
const closure = this.trackClosureState.get(t.trackId);
|
|
14939
|
+
const outcome = decideZeroMediaPolicy({
|
|
14940
|
+
durationMs: duration,
|
|
14941
|
+
hasMedia: (await this.mediaStore?.listByOwner("track", t.trackId) ?? []).length > 0,
|
|
14942
|
+
confirmed: closure?.confirmed ?? false,
|
|
14943
|
+
hasRasterFallback: closure?.rasterFallback !== void 0
|
|
14944
|
+
});
|
|
14945
|
+
if (outcome === "suppress") {
|
|
14946
|
+
this.ctx.logger.info("track suppressed (zero-media false birth)", {
|
|
14947
|
+
tags: { deviceId: t.deviceId },
|
|
14948
|
+
meta: {
|
|
14949
|
+
trackId: t.trackId,
|
|
14950
|
+
className: t.className,
|
|
14951
|
+
durationMs: duration,
|
|
14952
|
+
positions: t.positions.length
|
|
14953
|
+
}
|
|
14954
|
+
});
|
|
14955
|
+
await this.suppressZeroMediaTrack(t.deviceId, t.trackId);
|
|
14956
|
+
continue;
|
|
14957
|
+
}
|
|
14958
|
+
if (outcome === "raster-fallback" && closure?.rasterFallback) try {
|
|
14959
|
+
await this.mediaStore?.put({
|
|
14960
|
+
deviceId: t.deviceId,
|
|
14961
|
+
ownerKind: "track",
|
|
14962
|
+
ownerId: t.trackId,
|
|
14963
|
+
kind: "thumbnail",
|
|
14964
|
+
timestamp: closure.rasterFallback.timestamp,
|
|
14965
|
+
data: closure.rasterFallback.jpeg
|
|
14966
|
+
});
|
|
14967
|
+
} catch (err) {
|
|
14968
|
+
this.ctx.logger.debug("zero-media raster fallback put failed", {
|
|
14969
|
+
tags: { deviceId: t.deviceId },
|
|
14970
|
+
meta: {
|
|
14971
|
+
trackId: t.trackId,
|
|
14972
|
+
error: String(err)
|
|
14973
|
+
}
|
|
14974
|
+
});
|
|
14975
|
+
}
|
|
14707
14976
|
this.ctx.logger.info("track ended", {
|
|
14708
14977
|
tags: { deviceId: t.deviceId },
|
|
14709
14978
|
meta: {
|
|
@@ -14761,6 +15030,9 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
14761
15030
|
this.bestFrameTracker.delete(t.trackId);
|
|
14762
15031
|
this.objectEmbeddingBestSelector.delete(t.trackId);
|
|
14763
15032
|
this.lastFrameAtByTrack.delete(t.trackId);
|
|
15033
|
+
this.lastFrameInFlight.delete(t.trackId);
|
|
15034
|
+
this.firstFramePendingTracks.delete(t.trackId);
|
|
15035
|
+
this.firstFrameInFlight.delete(t.trackId);
|
|
14764
15036
|
this.thumbnailLandedTracks.delete(t.trackId);
|
|
14765
15037
|
this.thumbnailInFlight.delete(t.trackId);
|
|
14766
15038
|
this.keyFrameInFlight.delete(t.trackId);
|
|
@@ -14814,12 +15086,56 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
14814
15086
|
});
|
|
14815
15087
|
this.emitTrackLifecycle(endPayload, t.lastSeen);
|
|
14816
15088
|
this.trackLifecycleUpdateMem.delete(t.trackId);
|
|
15089
|
+
this.trackClosureState.delete(t.trackId);
|
|
14817
15090
|
}
|
|
14818
15091
|
} catch (err) {
|
|
14819
15092
|
if (this.shuttingDown) return;
|
|
14820
15093
|
this.ctx.logger.debug("sweepExpiredTracks failed", { meta: { error: String(err) } });
|
|
14821
15094
|
}
|
|
14822
15095
|
}
|
|
15096
|
+
/**
|
|
15097
|
+
* Undo a zero-media false-birth track (spec §"Zero-media track policy"): tear
|
|
15098
|
+
* down its live per-track state (mirroring the natural end MINUS importance
|
|
15099
|
+
* scoring + the lifecycle `end`/`TrackEnded` events) and cascade-delete the
|
|
15100
|
+
* just-persisted row + any events/media so nothing dangles. Best-effort.
|
|
15101
|
+
*/
|
|
15102
|
+
async suppressZeroMediaTrack(deviceId, trackId) {
|
|
15103
|
+
const faceEnd = this.faceRecognizer?.onTrackEnd(deviceId, trackId);
|
|
15104
|
+
const dropKeyFrame = () => {
|
|
15105
|
+
this.keyFrameKeyByTrackId.delete(trackId);
|
|
15106
|
+
};
|
|
15107
|
+
if (faceEnd) faceEnd.finally(dropKeyFrame);
|
|
15108
|
+
else dropKeyFrame();
|
|
15109
|
+
this.plateRecognizer?.onTrackEnd(deviceId, trackId);
|
|
15110
|
+
this.bestFrameTracker.delete(trackId);
|
|
15111
|
+
this.objectEmbeddingBestSelector.delete(trackId);
|
|
15112
|
+
this.lastFrameAtByTrack.delete(trackId);
|
|
15113
|
+
this.lastFrameInFlight.delete(trackId);
|
|
15114
|
+
this.firstFramePendingTracks.delete(trackId);
|
|
15115
|
+
this.firstFrameInFlight.delete(trackId);
|
|
15116
|
+
this.thumbnailLandedTracks.delete(trackId);
|
|
15117
|
+
this.thumbnailInFlight.delete(trackId);
|
|
15118
|
+
this.keyFrameInFlight.delete(trackId);
|
|
15119
|
+
this.detailDispatcher?.onTrackEnded(deviceId, trackId);
|
|
15120
|
+
this.overlayState.onTrackEnded(deviceId, trackId);
|
|
15121
|
+
this.trackLifecycleUpdateMem.delete(trackId);
|
|
15122
|
+
this.trackClosureState.delete(trackId);
|
|
15123
|
+
if (this.eventStore && this.mediaStore && this.trackStore) try {
|
|
15124
|
+
await cascadeDeleteTrack({
|
|
15125
|
+
eventStore: this.eventStore,
|
|
15126
|
+
mediaStore: this.mediaStore,
|
|
15127
|
+
trackStore: this.trackStore
|
|
15128
|
+
}, trackId);
|
|
15129
|
+
} catch (err) {
|
|
15130
|
+
this.ctx.logger.debug("zero-media suppression cascade failed", {
|
|
15131
|
+
tags: { deviceId },
|
|
15132
|
+
meta: {
|
|
15133
|
+
trackId,
|
|
15134
|
+
error: String(err)
|
|
15135
|
+
}
|
|
15136
|
+
});
|
|
15137
|
+
}
|
|
15138
|
+
}
|
|
14823
15139
|
async sweepRetention() {
|
|
14824
15140
|
if (this.shuttingDown || !this.eventStore || !this.mediaStore) return;
|
|
14825
15141
|
const now = Date.now();
|
|
@@ -15025,10 +15341,14 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
15025
15341
|
this.bestFrameTracker.delete(track.trackId);
|
|
15026
15342
|
this.objectEmbeddingBestSelector.delete(track.trackId);
|
|
15027
15343
|
this.lastFrameAtByTrack.delete(track.trackId);
|
|
15344
|
+
this.lastFrameInFlight.delete(track.trackId);
|
|
15345
|
+
this.firstFramePendingTracks.delete(track.trackId);
|
|
15346
|
+
this.firstFrameInFlight.delete(track.trackId);
|
|
15028
15347
|
this.thumbnailLandedTracks.delete(track.trackId);
|
|
15029
15348
|
this.thumbnailInFlight.delete(track.trackId);
|
|
15030
15349
|
this.keyFrameInFlight.delete(track.trackId);
|
|
15031
15350
|
this.trackLifecycleUpdateMem.delete(track.trackId);
|
|
15351
|
+
this.trackClosureState.delete(track.trackId);
|
|
15032
15352
|
this.detailDispatcher?.onTrackEnded(deviceId, track.trackId);
|
|
15033
15353
|
this.overlayState.onTrackEnded(deviceId, track.trackId);
|
|
15034
15354
|
this.lastActiveTrackIds.get(key)?.delete(track.trackId);
|
|
@@ -15327,6 +15647,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
15327
15647
|
}
|
|
15328
15648
|
async clearTracks(input) {
|
|
15329
15649
|
this.trackStore?.clearDevice(input.deviceId);
|
|
15650
|
+
this.clearClosureStateForDevice(input.deviceId);
|
|
15330
15651
|
this.stationaryRegistry?.clearDevice(input.deviceId);
|
|
15331
15652
|
this.overlayState.clearDevice(input.deviceId);
|
|
15332
15653
|
this.overlaySynthesisWarnAt.delete(input.deviceId);
|
|
@@ -15382,7 +15703,9 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
15382
15703
|
return input.kind ? all.filter((m) => m.kind === input.kind) : all;
|
|
15383
15704
|
}
|
|
15384
15705
|
async getTrackMedia(input) {
|
|
15385
|
-
|
|
15706
|
+
const all = await (this.mediaStore?.listByOwner("track", input.trackId) ?? Promise.resolve([]));
|
|
15707
|
+
const kinds = input.kinds;
|
|
15708
|
+
return kinds && kinds.length > 0 ? all.filter((m) => kinds.includes(m.kind)) : all;
|
|
15386
15709
|
}
|
|
15387
15710
|
/**
|
|
15388
15711
|
* Search object events by text using CLIP cosine similarity.
|
|
@@ -15539,6 +15862,27 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
15539
15862
|
clearLiveTrackState(deviceId, trackId) {
|
|
15540
15863
|
this.detailDispatcher?.onTrackEnded(deviceId, trackId);
|
|
15541
15864
|
this.overlayState.onTrackEnded(deviceId, trackId);
|
|
15865
|
+
this.trackClosureState.delete(trackId);
|
|
15866
|
+
}
|
|
15867
|
+
/** Lazily create + return a track's close-policy state (zero-media policy). */
|
|
15868
|
+
ensureTrackClosureState(deviceId, trackId) {
|
|
15869
|
+
let st = this.trackClosureState.get(trackId);
|
|
15870
|
+
if (!st) {
|
|
15871
|
+
st = {
|
|
15872
|
+
deviceId,
|
|
15873
|
+
confirmed: false
|
|
15874
|
+
};
|
|
15875
|
+
this.trackClosureState.set(trackId, st);
|
|
15876
|
+
}
|
|
15877
|
+
return st;
|
|
15878
|
+
}
|
|
15879
|
+
/** Sticky-mark a track as confirmed once it reaches the tracker's hit gate. */
|
|
15880
|
+
markTrackConfirmed(deviceId, trackId) {
|
|
15881
|
+
this.ensureTrackClosureState(deviceId, trackId).confirmed = true;
|
|
15882
|
+
}
|
|
15883
|
+
/** Drop every close-policy entry for a device (mirrors `trackStore.clearDevice`). */
|
|
15884
|
+
clearClosureStateForDevice(deviceId) {
|
|
15885
|
+
for (const [trackId, st] of this.trackClosureState) if (st.deviceId === deviceId) this.trackClosureState.delete(trackId);
|
|
15542
15886
|
}
|
|
15543
15887
|
async deleteTracks(input) {
|
|
15544
15888
|
const cascade = this.buildTrackCascadeRegistry();
|
|
@@ -15833,11 +16177,11 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
15833
16177
|
const eventFiles = await (this.mediaStore?.listByOwner("event", id) ?? Promise.resolve([]));
|
|
15834
16178
|
if (preferKind !== void 0 && preferKind.length > 0) {
|
|
15835
16179
|
const trackFiles = await (this.mediaStore?.listByOwner("track", id) ?? Promise.resolve([]));
|
|
15836
|
-
const
|
|
15837
|
-
if (!
|
|
16180
|
+
const chosen = resolveCropForcedMedia([...eventFiles, ...trackFiles], (files) => pickCleanMedia(files, preferKind));
|
|
16181
|
+
if (!chosen) return null;
|
|
15838
16182
|
return {
|
|
15839
|
-
bytes: Buffer.from(
|
|
15840
|
-
key:
|
|
16183
|
+
bytes: Buffer.from(chosen.base64, "base64"),
|
|
16184
|
+
key: chosen.key
|
|
15841
16185
|
};
|
|
15842
16186
|
}
|
|
15843
16187
|
const chosen = await resolveDefaultEventMedia({
|