@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as hydrateSchema, D as object, E as number, O as string, S as createEvent, T as boolean, _ as videoclipsCapability, a as OpsLogEntrySchema, b as BaseAddon, c as buildEventKindDescriptor, d as faceGalleryCapability, g as subKindsOf, h as plateGalleryCapability, i as MACRO_LABELS, k as EventCategory, l as cosineSimilarity, m as pipelineAnalyticsCapability, n as EVENT_KIND_BY_CAP, o as addonWidgetsSourceCapability, p as nodePin, r as EVENT_PAD_MS, s as audioMetricsCapability, t as DEFAULT_EVENT_COLOR, v as zoneAnalyticsCapability, w as array, x as DeviceType, y as errMsg } from "../dist-
|
|
1
|
+
import { C as hydrateSchema, D as object, E as number, O as string, S as createEvent, T as boolean, _ as videoclipsCapability, a as OpsLogEntrySchema, b as BaseAddon, c as buildEventKindDescriptor, d as faceGalleryCapability, g as subKindsOf, h as plateGalleryCapability, i as MACRO_LABELS, k as EventCategory, l as cosineSimilarity, m as pipelineAnalyticsCapability, n as EVENT_KIND_BY_CAP, o as addonWidgetsSourceCapability, p as nodePin, r as EVENT_PAD_MS, s as audioMetricsCapability, t as DEFAULT_EVENT_COLOR, v as zoneAnalyticsCapability, w as array, x as DeviceType, y as errMsg } from "../dist-Db0CsDGK.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import sharp from "sharp";
|
|
4
4
|
//#region src/pipeline-analytics/videoclips-provider.ts
|
|
@@ -3355,6 +3355,24 @@ async function cascadeDeleteTracks(registry, trackStore, trackIds) {
|
|
|
3355
3355
|
for (const trackId of trackIds) await trackStore.deletePersisted(trackId);
|
|
3356
3356
|
}
|
|
3357
3357
|
/**
|
|
3358
|
+
* Full whole-track cascade for ONE track: events → their media + track media →
|
|
3359
|
+
* track row. Delegates to the generalized `cascadeDeleteTracks` so the shipped
|
|
3360
|
+
* `deleteTracks` provider path flows through the same engine while preserving
|
|
3361
|
+
* the exact legacy call order (event ids stay local to the delete). Adapts the
|
|
3362
|
+
* narrow legacy store shapes onto `TrackScopedStore` — a superset store (faces /
|
|
3363
|
+
* embeddings) is opted in by the CALLER's registry, not by this legacy shim.
|
|
3364
|
+
*/
|
|
3365
|
+
async function cascadeDeleteTrack(stores, trackId) {
|
|
3366
|
+
await cascadeDeleteTracks([{ deleteByTracks: async (ids) => {
|
|
3367
|
+
for (const id of ids) {
|
|
3368
|
+
const eventIds = await stores.eventStore.deleteByTrack(id);
|
|
3369
|
+
if (eventIds.length > 0) await stores.mediaStore.deleteForEvents([...eventIds]);
|
|
3370
|
+
}
|
|
3371
|
+
} }, { deleteByTracks: async (ids) => {
|
|
3372
|
+
await stores.mediaStore.deleteForTracks([...ids]);
|
|
3373
|
+
} }], stores.trackStore, [trackId]);
|
|
3374
|
+
}
|
|
3375
|
+
/**
|
|
3358
3376
|
* Widened batch whole-track deletion — the ONE engine behind the three §5 entry
|
|
3359
3377
|
* points. Runs the FULL registry cascade per track (via `cascadeDeleteTracks`
|
|
3360
3378
|
* with a single-id list so the track root is deleted last), fires cleanup on
|
|
@@ -6473,6 +6491,36 @@ function squareSafeCropRegionNormalized(bbox, frame, padding) {
|
|
|
6473
6491
|
};
|
|
6474
6492
|
}
|
|
6475
6493
|
//#endregion
|
|
6494
|
+
//#region src/shared/frame/crop-extractor.ts
|
|
6495
|
+
/**
|
|
6496
|
+
* Extracts a JPEG-encoded crop from a raw frame buffer using a normalized bounding box.
|
|
6497
|
+
* Coordinates are clamped to frame bounds to avoid out-of-range errors.
|
|
6498
|
+
*/
|
|
6499
|
+
async function extractCrop(frameData, frameWidth, frameHeight, bbox) {
|
|
6500
|
+
const rawLeft = Math.round(bbox.x * frameWidth);
|
|
6501
|
+
const rawTop = Math.round(bbox.y * frameHeight);
|
|
6502
|
+
const rawWidth = Math.round(bbox.w * frameWidth);
|
|
6503
|
+
const rawHeight = Math.round(bbox.h * frameHeight);
|
|
6504
|
+
const left = Math.max(0, Math.min(rawLeft, frameWidth - 1));
|
|
6505
|
+
const top = Math.max(0, Math.min(rawTop, frameHeight - 1));
|
|
6506
|
+
const width = Math.max(1, Math.min(rawWidth, frameWidth - left));
|
|
6507
|
+
const height = Math.max(1, Math.min(rawHeight, frameHeight - top));
|
|
6508
|
+
return {
|
|
6509
|
+
crop: await sharp(frameData, { raw: {
|
|
6510
|
+
width: frameWidth,
|
|
6511
|
+
height: frameHeight,
|
|
6512
|
+
channels: 3
|
|
6513
|
+
} }).extract({
|
|
6514
|
+
left,
|
|
6515
|
+
top,
|
|
6516
|
+
width,
|
|
6517
|
+
height
|
|
6518
|
+
}).jpeg({ quality: 90 }).toBuffer(),
|
|
6519
|
+
width,
|
|
6520
|
+
height
|
|
6521
|
+
};
|
|
6522
|
+
}
|
|
6523
|
+
//#endregion
|
|
6476
6524
|
//#region src/shared/frame/square-subject-crop.ts
|
|
6477
6525
|
/** Expansion factor applied to the bbox long side to frame the subject with a
|
|
6478
6526
|
* little breathing room (operator choice: ×1.2). */
|
|
@@ -6711,9 +6759,13 @@ var EventMediaDispatcher = class {
|
|
|
6711
6759
|
const snapshots = input.snapshots ?? [];
|
|
6712
6760
|
const empty = {
|
|
6713
6761
|
storedSnapshots: [],
|
|
6714
|
-
thumbnailTrackIds: []
|
|
6762
|
+
thumbnailTrackIds: [],
|
|
6763
|
+
firstFrameTrackIds: [],
|
|
6764
|
+
lastFrameTrackIds: [],
|
|
6765
|
+
rasterFallbacks: []
|
|
6715
6766
|
};
|
|
6716
|
-
|
|
6767
|
+
const extraCandidateCount = input.rasterFallbackCandidates?.length ?? 0;
|
|
6768
|
+
if (events.length === 0 && trackFrames.length === 0 && snapshots.length === 0 && extraCandidateCount === 0) return empty;
|
|
6717
6769
|
let decoded;
|
|
6718
6770
|
try {
|
|
6719
6771
|
decoded = await resolveFrame(frameHandle, { getRemoteFrame: this.deps.getRemoteFrame });
|
|
@@ -6763,21 +6815,82 @@ var EventMediaDispatcher = class {
|
|
|
6763
6815
|
});
|
|
6764
6816
|
return empty;
|
|
6765
6817
|
}
|
|
6818
|
+
const rasterFallbacks = await this.collectRasterFallbacks(frameData, fw, fh, trackFrames, snapshots, input.cropPadding, input.rasterFallbackWantedTrackIds, input.rasterFallbackCandidates);
|
|
6766
6819
|
for (const ev of events) await this.writeEventMedia(deviceId, frameHandle, fw, fh, ev, input.cropPadding);
|
|
6767
|
-
|
|
6820
|
+
const firstFrameTrackIds = [];
|
|
6821
|
+
for (const tf of trackFrames) if (await this.writeTrackFrame(deviceId, frameHandle, frameData, fw, fh, tf)) firstFrameTrackIds.push(tf.trackId);
|
|
6768
6822
|
const storedSnapshots = [];
|
|
6769
6823
|
const thumbnailTrackIds = [];
|
|
6824
|
+
const lastFrameTrackIds = [];
|
|
6770
6825
|
for (const sn of snapshots) {
|
|
6771
6826
|
const res = await this.writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn);
|
|
6772
6827
|
if (res.storedSnapshot) storedSnapshots.push(res.storedSnapshot);
|
|
6773
6828
|
if (res.thumbnailWritten) thumbnailTrackIds.push(sn.trackId);
|
|
6829
|
+
if (res.lastFrameWritten) lastFrameTrackIds.push(sn.trackId);
|
|
6774
6830
|
}
|
|
6775
6831
|
return {
|
|
6776
6832
|
storedSnapshots,
|
|
6777
|
-
thumbnailTrackIds
|
|
6833
|
+
thumbnailTrackIds,
|
|
6834
|
+
firstFrameTrackIds,
|
|
6835
|
+
lastFrameTrackIds,
|
|
6836
|
+
rasterFallbacks
|
|
6778
6837
|
};
|
|
6779
6838
|
}
|
|
6780
6839
|
/**
|
|
6840
|
+
* Cut ONE clean detection-raster subject crop per WANTED track observed this
|
|
6841
|
+
* frame — the "first available detection-raster frame" of the zero-media
|
|
6842
|
+
* fallback. Candidate bboxes come from this frame's firstFrame/snapshot targets
|
|
6843
|
+
* AND (widened) the explicit `extraCandidates` (confirmed tracks with no such
|
|
6844
|
+
* target). Cropped from the already-resolved `frameData` at its REAL resolution
|
|
6845
|
+
* via {@link extractCrop} (extract-only — NEVER upscaled). Deduped per trackId
|
|
6846
|
+
* (first candidate wins; target-derived candidates precede the extras). A
|
|
6847
|
+
* per-track encode failure is skipped (logged) — a missing fallback simply
|
|
6848
|
+
* leaves the track with no last-resort preview, never an error.
|
|
6849
|
+
*/
|
|
6850
|
+
async collectRasterFallbacks(frameData, fw, fh, trackFrames, snapshots, cropPadding, wanted, extraCandidates) {
|
|
6851
|
+
if (!wanted || wanted.size === 0) return [];
|
|
6852
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6853
|
+
const out = [];
|
|
6854
|
+
const candidates = [
|
|
6855
|
+
...trackFrames.map((t) => ({
|
|
6856
|
+
trackId: t.trackId,
|
|
6857
|
+
timestamp: t.timestamp,
|
|
6858
|
+
bbox: t.bbox
|
|
6859
|
+
})),
|
|
6860
|
+
...snapshots.map((s) => ({
|
|
6861
|
+
trackId: s.trackId,
|
|
6862
|
+
timestamp: s.timestamp,
|
|
6863
|
+
bbox: s.bbox
|
|
6864
|
+
})),
|
|
6865
|
+
...(extraCandidates ?? []).map((c) => ({
|
|
6866
|
+
trackId: c.trackId,
|
|
6867
|
+
timestamp: c.timestamp,
|
|
6868
|
+
bbox: c.bbox
|
|
6869
|
+
}))
|
|
6870
|
+
];
|
|
6871
|
+
for (const c of candidates) {
|
|
6872
|
+
if (!wanted.has(c.trackId) || seen.has(c.trackId)) continue;
|
|
6873
|
+
seen.add(c.trackId);
|
|
6874
|
+
try {
|
|
6875
|
+
const { crop } = await extractCrop(frameData, fw, fh, squareSafeCropRegionNormalized(c.bbox, {
|
|
6876
|
+
W: fw,
|
|
6877
|
+
H: fh
|
|
6878
|
+
}, cropPadding));
|
|
6879
|
+
out.push({
|
|
6880
|
+
trackId: c.trackId,
|
|
6881
|
+
timestamp: c.timestamp,
|
|
6882
|
+
jpeg: crop
|
|
6883
|
+
});
|
|
6884
|
+
} catch (err) {
|
|
6885
|
+
this.deps.logger.debug("event media: raster fallback crop failed", { meta: {
|
|
6886
|
+
trackId: c.trackId,
|
|
6887
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6888
|
+
} });
|
|
6889
|
+
}
|
|
6890
|
+
}
|
|
6891
|
+
return out;
|
|
6892
|
+
}
|
|
6893
|
+
/**
|
|
6781
6894
|
* Periodic per-track media (§5). The boxed FULL frame is encoded once and
|
|
6782
6895
|
* shared by the appended `snapshot` (timeline filmstrip) and the rolling
|
|
6783
6896
|
* `lastFrame` (overwrite). The best `thumbnail` is DIFFERENT: a clean
|
|
@@ -6788,11 +6901,15 @@ var EventMediaDispatcher = class {
|
|
|
6788
6901
|
* TrackStore wiring (null when `appendSnapshot` is false or the encode
|
|
6789
6902
|
* failed). `thumbnailWritten` reports whether a best `thumbnail` actually
|
|
6790
6903
|
* landed this frame (#27-A) so the caller can stop forcing retries.
|
|
6904
|
+
* `lastFrameWritten` reports whether the rolling `lastFrame` actually landed
|
|
6905
|
+
* (DEFECT B) so the caller advances its `lastFrameAt` clock ONLY on a real
|
|
6906
|
+
* write — a dropped roll leaves the clock put and retries next frame.
|
|
6791
6907
|
*/
|
|
6792
6908
|
async writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn) {
|
|
6793
6909
|
if (!sn.appendSnapshot && !sn.rollingLastFrame && !sn.bestThumbnail) return {
|
|
6794
6910
|
storedSnapshot: null,
|
|
6795
|
-
thumbnailWritten: false
|
|
6911
|
+
thumbnailWritten: false,
|
|
6912
|
+
lastFrameWritten: false
|
|
6796
6913
|
};
|
|
6797
6914
|
let boxed = null;
|
|
6798
6915
|
if (sn.appendSnapshot || sn.rollingLastFrame) boxed = await this.boxedTimelineFrame(deviceId, frameHandle, frameData, fw, fh, sn.trackId, sn.bbox, sn.label);
|
|
@@ -6813,7 +6930,8 @@ var EventMediaDispatcher = class {
|
|
|
6813
6930
|
bbox: sn.bbox
|
|
6814
6931
|
};
|
|
6815
6932
|
} catch {}
|
|
6816
|
-
|
|
6933
|
+
let lastFrameWritten = false;
|
|
6934
|
+
if (sn.rollingLastFrame && boxed) lastFrameWritten = await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
|
|
6817
6935
|
let thumbnailWritten = false;
|
|
6818
6936
|
if (sn.bestThumbnail) {
|
|
6819
6937
|
const variants = await this.cropSubjectVariants(frameHandle, fw, fh, sn.bbox);
|
|
@@ -6824,7 +6942,8 @@ var EventMediaDispatcher = class {
|
|
|
6824
6942
|
}
|
|
6825
6943
|
return {
|
|
6826
6944
|
storedSnapshot: stored,
|
|
6827
|
-
thumbnailWritten
|
|
6945
|
+
thumbnailWritten,
|
|
6946
|
+
lastFrameWritten
|
|
6828
6947
|
};
|
|
6829
6948
|
}
|
|
6830
6949
|
/**
|
|
@@ -7021,7 +7140,7 @@ var EventMediaDispatcher = class {
|
|
|
7021
7140
|
}
|
|
7022
7141
|
async writeTrackFrame(deviceId, frameHandle, frameData, fw, fh, tf) {
|
|
7023
7142
|
const boxed = await this.boxedTimelineFrame(deviceId, frameHandle, frameData, fw, fh, tf.trackId, tf.bbox, tf.label);
|
|
7024
|
-
if (!boxed) return;
|
|
7143
|
+
if (!boxed) return false;
|
|
7025
7144
|
try {
|
|
7026
7145
|
await this.deps.mediaStore.put({
|
|
7027
7146
|
deviceId,
|
|
@@ -7031,6 +7150,7 @@ var EventMediaDispatcher = class {
|
|
|
7031
7150
|
timestamp: tf.timestamp,
|
|
7032
7151
|
data: boxed
|
|
7033
7152
|
});
|
|
7153
|
+
return true;
|
|
7034
7154
|
} catch (err) {
|
|
7035
7155
|
this.deps.logger.warn("event media: track frame failed", {
|
|
7036
7156
|
tags: { deviceId },
|
|
@@ -7040,40 +7160,11 @@ var EventMediaDispatcher = class {
|
|
|
7040
7160
|
error: err instanceof Error ? err.message : String(err)
|
|
7041
7161
|
}
|
|
7042
7162
|
});
|
|
7163
|
+
return false;
|
|
7043
7164
|
}
|
|
7044
7165
|
}
|
|
7045
7166
|
};
|
|
7046
7167
|
//#endregion
|
|
7047
|
-
//#region src/shared/frame/crop-extractor.ts
|
|
7048
|
-
/**
|
|
7049
|
-
* Extracts a JPEG-encoded crop from a raw frame buffer using a normalized bounding box.
|
|
7050
|
-
* Coordinates are clamped to frame bounds to avoid out-of-range errors.
|
|
7051
|
-
*/
|
|
7052
|
-
async function extractCrop(frameData, frameWidth, frameHeight, bbox) {
|
|
7053
|
-
const rawLeft = Math.round(bbox.x * frameWidth);
|
|
7054
|
-
const rawTop = Math.round(bbox.y * frameHeight);
|
|
7055
|
-
const rawWidth = Math.round(bbox.w * frameWidth);
|
|
7056
|
-
const rawHeight = Math.round(bbox.h * frameHeight);
|
|
7057
|
-
const left = Math.max(0, Math.min(rawLeft, frameWidth - 1));
|
|
7058
|
-
const top = Math.max(0, Math.min(rawTop, frameHeight - 1));
|
|
7059
|
-
const width = Math.max(1, Math.min(rawWidth, frameWidth - left));
|
|
7060
|
-
const height = Math.max(1, Math.min(rawHeight, frameHeight - top));
|
|
7061
|
-
return {
|
|
7062
|
-
crop: await sharp(frameData, { raw: {
|
|
7063
|
-
width: frameWidth,
|
|
7064
|
-
height: frameHeight,
|
|
7065
|
-
channels: 3
|
|
7066
|
-
} }).extract({
|
|
7067
|
-
left,
|
|
7068
|
-
top,
|
|
7069
|
-
width,
|
|
7070
|
-
height
|
|
7071
|
-
}).jpeg({ quality: 90 }).toBuffer(),
|
|
7072
|
-
width,
|
|
7073
|
-
height
|
|
7074
|
-
};
|
|
7075
|
-
}
|
|
7076
|
-
//#endregion
|
|
7077
7168
|
//#region src/pipeline-analytics/embedding/embedding-dispatcher.ts
|
|
7078
7169
|
function selectCropBbox(detection) {
|
|
7079
7170
|
return detection.refinedBbox ?? detection.bbox;
|
|
@@ -8862,6 +8953,16 @@ function planPeriodicMedia(input) {
|
|
|
8862
8953
|
bestThumbnail: input.isNewBest || !thumbnailLanded
|
|
8863
8954
|
};
|
|
8864
8955
|
}
|
|
8956
|
+
var DEFAULT_ZERO_MEDIA_POLICY_CONFIG = { suppressMaxDurationMs: 1e3 };
|
|
8957
|
+
/**
|
|
8958
|
+
* Classify a closing track's persistence outcome. Pure — see the module header
|
|
8959
|
+
* for the full contract.
|
|
8960
|
+
*/
|
|
8961
|
+
function decideZeroMediaPolicy(input, config = DEFAULT_ZERO_MEDIA_POLICY_CONFIG) {
|
|
8962
|
+
if (input.hasMedia) return "persist";
|
|
8963
|
+
if (input.durationMs < config.suppressMaxDurationMs && !input.confirmed) return "suppress";
|
|
8964
|
+
return input.hasRasterFallback ? "raster-fallback" : "persist";
|
|
8965
|
+
}
|
|
8865
8966
|
//#endregion
|
|
8866
8967
|
//#region src/pipeline-analytics/best-thumbnail-guard.ts
|
|
8867
8968
|
/**
|
|
@@ -12121,6 +12222,23 @@ function pickTrackFallbackMedia(files) {
|
|
|
12121
12222
|
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];
|
|
12122
12223
|
}
|
|
12123
12224
|
/**
|
|
12225
|
+
* Crop-forced resolution (the `?kind=crop` reel/timeline path). Prefer a CLEAN
|
|
12226
|
+
* (never-boxed) frame via the injected `pickClean`; when the track owns NO clean
|
|
12227
|
+
* frame at all — the regression class created by retiring `crop`/`fullFrame`
|
|
12228
|
+
* production (2026-07-21), leaving degenerate tracks with only boxed
|
|
12229
|
+
* `lastFrame`/`snapshot`/`firstFrame` — degrade to ANY track media rather than
|
|
12230
|
+
* returning undefined. A boxed frame (with the target's box drawn) is a real
|
|
12231
|
+
* preview; an empty tile is not. Clean is always tried FIRST so the common case
|
|
12232
|
+
* still serves an unboxed tile. Undefined only when the owner has NO media.
|
|
12233
|
+
*
|
|
12234
|
+
* `pickClean` is injected because the clean-kind picker (`pickCleanMedia`) lives
|
|
12235
|
+
* in the addon `index.ts` over the concrete `MediaFileKind` set; this keeps the
|
|
12236
|
+
* composition pure + unit-testable here.
|
|
12237
|
+
*/
|
|
12238
|
+
function resolveCropForcedMedia(files, pickClean) {
|
|
12239
|
+
return pickClean(files) ?? pickTrackFallbackMedia(files);
|
|
12240
|
+
}
|
|
12241
|
+
/**
|
|
12124
12242
|
* Resolve the default-path media for an event id: the event's own media when it
|
|
12125
12243
|
* has any, else the owning track's fallback media. Returns `null` when neither
|
|
12126
12244
|
* yields a blob (caller 404s → the surface shows an icon).
|
|
@@ -12452,6 +12570,32 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
12452
12570
|
* where no `snapshot` is appended, so it is never byte-identical to a stored
|
|
12453
12571
|
* `snapshot` (kills the end-of-track duplicate). Cleared on track end. */
|
|
12454
12572
|
lastFrameAtByTrack = /* @__PURE__ */ new Map();
|
|
12573
|
+
/** Tracks with a rolling-`lastFrame` capture CURRENTLY in flight (RC-1,
|
|
12574
|
+
* DEFECT B). The `lastFrameAt` clock now advances ONLY when the write lands
|
|
12575
|
+
* (dispatcher completion), not synchronously at plan time — so a dropped roll
|
|
12576
|
+
* (recycled/blank frame) leaves the clock put and re-rolls next frame. Without
|
|
12577
|
+
* an in-flight guard that re-roll would fire EVERY frame while the first
|
|
12578
|
+
* capture is still resolving (0.1–3s under the native path), stacking N
|
|
12579
|
+
* overlapping captures. While a track sits here `buildSnapshotTargets`
|
|
12580
|
+
* suppresses a new rolling-`lastFrame` request; the pending dispatch clears it
|
|
12581
|
+
* (and, if it landed, advances `lastFrameAtByTrack`). Cleared on track end +
|
|
12582
|
+
* reset. */
|
|
12583
|
+
lastFrameInFlight = /* @__PURE__ */ new Set();
|
|
12584
|
+
/** Confirmed-birth tracks whose `firstFrame` has NOT yet actually persisted
|
|
12585
|
+
* (DEFECT A). Seeded on a confirmed birth (alongside the birth firstFrame
|
|
12586
|
+
* target) and removed the moment the write lands. While a track sits here and
|
|
12587
|
+
* is matched this frame, `collectFirstFrameRetries` re-schedules a firstFrame
|
|
12588
|
+
* target so a birth capture dropped by a recycled/blank live frame is retried
|
|
12589
|
+
* on a later frame (earliest available view still beats none). Scoped to
|
|
12590
|
+
* confirmed births ONLY — a suppressed false-positive birth or resurrection
|
|
12591
|
+
* never enters, so it never gets a retro firstFrame. Cleared on track end +
|
|
12592
|
+
* reset. */
|
|
12593
|
+
firstFramePendingTracks = /* @__PURE__ */ new Set();
|
|
12594
|
+
/** Tracks with a `firstFrame` capture CURRENTLY in flight (RC-1, DEFECT A).
|
|
12595
|
+
* Mirrors `thumbnailInFlight`: while a firstFrame capture is resolving the
|
|
12596
|
+
* per-frame retry is suppressed so at most one capture is outstanding per
|
|
12597
|
+
* track. Cleared on dispatch settle (+ track end / reset). */
|
|
12598
|
+
firstFrameInFlight = /* @__PURE__ */ new Set();
|
|
12455
12599
|
/** Track ids whose best `thumbnail` has ACTUALLY been persisted (#27-A). A
|
|
12456
12600
|
* track absent here keeps forcing a best-thumbnail capture every frame until
|
|
12457
12601
|
* one lands, so a short / high-churn track whose first capture was dropped
|
|
@@ -12476,6 +12620,14 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
12476
12620
|
* area) + emit time, so a material improvement is measured against the
|
|
12477
12621
|
* last emit and debounced. Seeded at `start`, dropped at `end`. */
|
|
12478
12622
|
trackLifecycleUpdateMem = /* @__PURE__ */ new Map();
|
|
12623
|
+
/** Per-active-track close-policy state (spec §"Zero-media track policy"):
|
|
12624
|
+
* whether the track ever reached the tracker's hit-confirmation
|
|
12625
|
+
* (`trackAge >= minHits`) and the FIRST retained detection-raster subject
|
|
12626
|
+
* crop. Both are read ONLY at close (`sweepExpiredTracks`) to decide
|
|
12627
|
+
* suppress / raster-fallback / persist for a track that ends with no media.
|
|
12628
|
+
* `deviceId` lets `clearDevice` prune without an active-track lookup. Dropped
|
|
12629
|
+
* on every teardown path. */
|
|
12630
|
+
trackClosureState = /* @__PURE__ */ new Map();
|
|
12479
12631
|
/** The shared crop extractor (native-res first, detection-frame fallback),
|
|
12480
12632
|
* captured in the constructor so `processFrame` can crop object thumbnails in
|
|
12481
12633
|
* the same live-frame window as the face/plate/event-media captures. The
|
|
@@ -12967,6 +13119,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
12967
13119
|
this.bindingCache?.onBindingsChanged(data);
|
|
12968
13120
|
if (data.capName === "pipeline-analytics" && data.reason === "wrapper-deactivated") {
|
|
12969
13121
|
this.trackStore?.clearDevice(data.deviceId);
|
|
13122
|
+
this.clearClosureStateForDevice(data.deviceId);
|
|
12970
13123
|
this.stationaryRegistry?.forgetDevice(data.deviceId);
|
|
12971
13124
|
this.overlayState.clearDevice(data.deviceId);
|
|
12972
13125
|
this.overlaySynthesisWarnAt.delete(data.deviceId);
|
|
@@ -12985,6 +13138,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
12985
13138
|
this.unsubDeviceUnreg = this.ctx.eventBus.subscribe({ category: EventCategory.DeviceUnregistered }, (ev) => {
|
|
12986
13139
|
const { deviceId } = ev.data;
|
|
12987
13140
|
this.trackStore?.clearDevice(deviceId);
|
|
13141
|
+
this.clearClosureStateForDevice(deviceId);
|
|
12988
13142
|
this.stationaryRegistry?.forgetDevice(deviceId);
|
|
12989
13143
|
this.overlayState.clearDevice(deviceId);
|
|
12990
13144
|
this.overlaySynthesisWarnAt.delete(deviceId);
|
|
@@ -13336,10 +13490,14 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13336
13490
|
this.dropoutSkipsByKey.clear();
|
|
13337
13491
|
this.bestFrameTracker.clear();
|
|
13338
13492
|
this.lastFrameAtByTrack.clear();
|
|
13493
|
+
this.lastFrameInFlight.clear();
|
|
13494
|
+
this.firstFramePendingTracks.clear();
|
|
13495
|
+
this.firstFrameInFlight.clear();
|
|
13339
13496
|
this.thumbnailLandedTracks.clear();
|
|
13340
13497
|
this.thumbnailInFlight.clear();
|
|
13341
13498
|
this.keyFrameInFlight.clear();
|
|
13342
13499
|
this.trackLifecycleUpdateMem.clear();
|
|
13500
|
+
this.trackClosureState.clear();
|
|
13343
13501
|
this.objectEmbeddingBestSelector.clear();
|
|
13344
13502
|
this.levelStateByDevice.clear();
|
|
13345
13503
|
this.settingsCacheByDevice.clear();
|
|
@@ -13458,6 +13616,10 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13458
13616
|
});
|
|
13459
13617
|
positionsCountById.set(t.trackId, upserted.positions.length);
|
|
13460
13618
|
}
|
|
13619
|
+
if (result.rawTrackedDetections.length > 0) {
|
|
13620
|
+
const { minHits } = await this.resolveDeviceDetectionSensitivitySettings(deviceId);
|
|
13621
|
+
for (const rt of result.rawTrackedDetections) if (rt.trackAge >= minHits) this.markTrackConfirmed(deviceId, rt.trackId);
|
|
13622
|
+
}
|
|
13461
13623
|
const log = this.ctx.logger.withTags({ deviceId });
|
|
13462
13624
|
const prevIds = this.lastActiveTrackIds.get(key) ?? /* @__PURE__ */ new Set();
|
|
13463
13625
|
const firstFrameTargets = [];
|
|
@@ -13502,14 +13664,16 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13502
13664
|
className: t.className,
|
|
13503
13665
|
source
|
|
13504
13666
|
} });
|
|
13505
|
-
if (this.eventMediaDispatcher
|
|
13506
|
-
|
|
13667
|
+
if (this.eventMediaDispatcher) {
|
|
13668
|
+
this.firstFramePendingTracks.add(id);
|
|
13669
|
+
this.trackStore.seedSnapshotClock(id, result.timestamp, t.bbox);
|
|
13670
|
+
this.lastFrameAtByTrack.set(id, result.timestamp);
|
|
13671
|
+
if (frameHandle) firstFrameTargets.push({
|
|
13507
13672
|
trackId: id,
|
|
13508
13673
|
timestamp: result.timestamp,
|
|
13509
13674
|
bbox: { ...t.bbox },
|
|
13510
13675
|
...t.label ? { label: t.label } : {}
|
|
13511
13676
|
});
|
|
13512
|
-
this.trackStore.seedSnapshotClock(id, result.timestamp, t.bbox);
|
|
13513
13677
|
}
|
|
13514
13678
|
this.ctx.eventBus.emit({
|
|
13515
13679
|
id: `pa-${randomUUID()}`,
|
|
@@ -13658,6 +13822,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13658
13822
|
for (const crops of childCropsByEvent.values()) for (const c of crops) if (c.kind === "faceCrop") faceCrops += 1;
|
|
13659
13823
|
else plateCrops += 1;
|
|
13660
13824
|
const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight);
|
|
13825
|
+
for (const retry of this.collectFirstFrameRetries(result.tracked, result.timestamp, firstFrameTargets)) firstFrameTargets.push(retry);
|
|
13661
13826
|
const keyFrameTrackIds = selectKeyFrameTrackIds(snapshotTargets);
|
|
13662
13827
|
if (keyFrameTrackIds.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameTrackIds, frameHandle);
|
|
13663
13828
|
if (eventTargets.length > 0 || firstFrameTargets.length > 0 || snapshotTargets.length > 0) {
|
|
@@ -13681,14 +13846,46 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13681
13846
|
} });
|
|
13682
13847
|
const thumbInFlightTrackIds = snapshotTargets.filter((t) => t.bestThumbnail).map((t) => t.trackId);
|
|
13683
13848
|
for (const trackId of thumbInFlightTrackIds) this.thumbnailInFlight.add(trackId);
|
|
13849
|
+
const firstFrameInFlightTrackIds = firstFrameTargets.map((t) => t.trackId);
|
|
13850
|
+
for (const trackId of firstFrameInFlightTrackIds) this.firstFrameInFlight.add(trackId);
|
|
13851
|
+
const lastFrameInFlightTrackIds = snapshotTargets.filter((t) => t.rollingLastFrame).map((t) => t.trackId);
|
|
13852
|
+
for (const trackId of lastFrameInFlightTrackIds) this.lastFrameInFlight.add(trackId);
|
|
13853
|
+
const dispatchTimestamp = result.timestamp;
|
|
13854
|
+
const rasterFallbackWantedTrackIds = /* @__PURE__ */ new Set();
|
|
13855
|
+
for (const t of firstFrameTargets) if (!this.trackClosureState.get(t.trackId)?.rasterFallback) rasterFallbackWantedTrackIds.add(t.trackId);
|
|
13856
|
+
for (const s of snapshotTargets) if (!this.trackClosureState.get(s.trackId)?.rasterFallback) rasterFallbackWantedTrackIds.add(s.trackId);
|
|
13857
|
+
const targetedThisFrame = new Set([...firstFrameTargets.map((t) => t.trackId), ...snapshotTargets.map((s) => s.trackId)]);
|
|
13858
|
+
const rasterFallbackCandidates = [];
|
|
13859
|
+
for (const t of result.tracked) {
|
|
13860
|
+
if (t.matchedThisFrame === false) continue;
|
|
13861
|
+
const closure = this.trackClosureState.get(t.trackId);
|
|
13862
|
+
if (!closure?.confirmed) continue;
|
|
13863
|
+
if (closure.rasterFallback) continue;
|
|
13864
|
+
if (targetedThisFrame.has(t.trackId)) continue;
|
|
13865
|
+
rasterFallbackWantedTrackIds.add(t.trackId);
|
|
13866
|
+
rasterFallbackCandidates.push({
|
|
13867
|
+
trackId: t.trackId,
|
|
13868
|
+
timestamp: result.timestamp,
|
|
13869
|
+
bbox: { ...t.bbox }
|
|
13870
|
+
});
|
|
13871
|
+
}
|
|
13684
13872
|
this.eventMediaDispatcher.captureForFrame({
|
|
13685
13873
|
deviceId,
|
|
13686
13874
|
frameHandle,
|
|
13687
13875
|
events: eventTargets,
|
|
13688
13876
|
trackFrames: firstFrameTargets,
|
|
13689
13877
|
snapshots: snapshotTargets,
|
|
13690
|
-
cropPadding: mediaSettings.cropPadding
|
|
13878
|
+
cropPadding: mediaSettings.cropPadding,
|
|
13879
|
+
rasterFallbackWantedTrackIds,
|
|
13880
|
+
rasterFallbackCandidates
|
|
13691
13881
|
}).then((res) => {
|
|
13882
|
+
for (const rf of res.rasterFallbacks) {
|
|
13883
|
+
const st = this.ensureTrackClosureState(deviceId, rf.trackId);
|
|
13884
|
+
if (!st.rasterFallback) st.rasterFallback = {
|
|
13885
|
+
jpeg: rf.jpeg,
|
|
13886
|
+
timestamp: rf.timestamp
|
|
13887
|
+
};
|
|
13888
|
+
}
|
|
13692
13889
|
for (const s of res.storedSnapshots) this.trackStore?.addSnapshot(s.trackId, {
|
|
13693
13890
|
timestamp: s.timestamp,
|
|
13694
13891
|
position: {
|
|
@@ -13700,8 +13897,12 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13700
13897
|
mediaKey: s.mediaKey
|
|
13701
13898
|
});
|
|
13702
13899
|
for (const trackId of res.thumbnailTrackIds) this.thumbnailLandedTracks.add(trackId);
|
|
13900
|
+
for (const trackId of res.firstFrameTrackIds) this.firstFramePendingTracks.delete(trackId);
|
|
13901
|
+
for (const trackId of res.lastFrameTrackIds) this.lastFrameAtByTrack.set(trackId, dispatchTimestamp);
|
|
13703
13902
|
}).catch(() => {}).finally(() => {
|
|
13704
13903
|
for (const trackId of thumbInFlightTrackIds) this.thumbnailInFlight.delete(trackId);
|
|
13904
|
+
for (const trackId of firstFrameInFlightTrackIds) this.firstFrameInFlight.delete(trackId);
|
|
13905
|
+
for (const trackId of lastFrameInFlightTrackIds) this.lastFrameInFlight.delete(trackId);
|
|
13705
13906
|
});
|
|
13706
13907
|
}
|
|
13707
13908
|
}
|
|
@@ -13765,6 +13966,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13765
13966
|
}
|
|
13766
13967
|
overlayDetections = frame.detections;
|
|
13767
13968
|
}
|
|
13969
|
+
const hasMovingTrack = result.tracked.some((t) => t.state === "moving" || t.state === "entered" || t.state === "left");
|
|
13768
13970
|
this.ctx.eventBus.emit({
|
|
13769
13971
|
id: `pa-${randomUUID()}`,
|
|
13770
13972
|
timestamp: new Date(result.timestamp),
|
|
@@ -13779,7 +13981,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
13779
13981
|
timestamp: result.timestamp,
|
|
13780
13982
|
frameWidth: result.frameWidth,
|
|
13781
13983
|
frameHeight: result.frameHeight,
|
|
13782
|
-
detections: overlayDetections
|
|
13984
|
+
detections: overlayDetections,
|
|
13985
|
+
hasMovingTrack
|
|
13783
13986
|
}
|
|
13784
13987
|
});
|
|
13785
13988
|
}
|
|
@@ -14403,6 +14606,34 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
14403
14606
|
});
|
|
14404
14607
|
this.emitTrackLifecycle(payload, timestamp);
|
|
14405
14608
|
}
|
|
14609
|
+
/**
|
|
14610
|
+
* DEFECT A: build `firstFrame` RETRY targets for confirmed-birth tracks whose
|
|
14611
|
+
* birth capture never landed. A track qualifies when it is still pending
|
|
14612
|
+
* (`firstFramePendingTracks`), is OBSERVED this frame (`matchedThisFrame` — a
|
|
14613
|
+
* coasted/stale box would crop the empty scene), is NOT already targeted this
|
|
14614
|
+
* frame (its birth target), and has no capture in flight (RC-1). Each target is
|
|
14615
|
+
* stamped with the CURRENT frame timestamp so the media carries the real
|
|
14616
|
+
* capture instant, not the birth ts — the earliest AVAILABLE view still beats
|
|
14617
|
+
* no firstFrame at all.
|
|
14618
|
+
*/
|
|
14619
|
+
collectFirstFrameRetries(tracked, timestamp, alreadyTargeted) {
|
|
14620
|
+
if (this.firstFramePendingTracks.size === 0) return [];
|
|
14621
|
+
const bornThisFrame = new Set(alreadyTargeted.map((t) => t.trackId));
|
|
14622
|
+
const retries = [];
|
|
14623
|
+
for (const t of tracked) {
|
|
14624
|
+
if (t.matchedThisFrame === false) continue;
|
|
14625
|
+
if (!this.firstFramePendingTracks.has(t.trackId)) continue;
|
|
14626
|
+
if (bornThisFrame.has(t.trackId)) continue;
|
|
14627
|
+
if (this.firstFrameInFlight.has(t.trackId)) continue;
|
|
14628
|
+
retries.push({
|
|
14629
|
+
trackId: t.trackId,
|
|
14630
|
+
timestamp,
|
|
14631
|
+
bbox: { ...t.bbox },
|
|
14632
|
+
...t.label ? { label: t.label } : {}
|
|
14633
|
+
});
|
|
14634
|
+
}
|
|
14635
|
+
return retries;
|
|
14636
|
+
}
|
|
14406
14637
|
buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight) {
|
|
14407
14638
|
const targets = [];
|
|
14408
14639
|
for (const t of tracked) {
|
|
@@ -14436,19 +14667,19 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
14436
14667
|
now: timestamp,
|
|
14437
14668
|
intervalMs: media.snapshotIntervalMs
|
|
14438
14669
|
});
|
|
14439
|
-
|
|
14670
|
+
const rollingLastFrame = plan.rollingLastFrame && !this.lastFrameInFlight.has(t.trackId);
|
|
14440
14671
|
if (plan.appendSnapshot) this.trackStore.markSnapshotPending(t.trackId, timestamp, t.bbox);
|
|
14441
14672
|
const plausibleBox = isPlausibleThumbnailBox(t.bbox, frameWidth, frameHeight);
|
|
14442
14673
|
const bestThumbnail = plan.bestThumbnail && plausibleBox && !this.thumbnailInFlight.has(t.trackId);
|
|
14443
14674
|
const keyFrame = isNewBest && plausibleBox;
|
|
14444
|
-
if (!plan.appendSnapshot && !
|
|
14675
|
+
if (!plan.appendSnapshot && !rollingLastFrame && !bestThumbnail && !keyFrame) continue;
|
|
14445
14676
|
targets.push({
|
|
14446
14677
|
trackId: t.trackId,
|
|
14447
14678
|
timestamp,
|
|
14448
14679
|
bbox: { ...t.bbox },
|
|
14449
14680
|
...t.label ? { label: t.label } : {},
|
|
14450
14681
|
appendSnapshot: plan.appendSnapshot,
|
|
14451
|
-
rollingLastFrame
|
|
14682
|
+
rollingLastFrame,
|
|
14452
14683
|
bestThumbnail,
|
|
14453
14684
|
keyFrame
|
|
14454
14685
|
});
|
|
@@ -14699,6 +14930,44 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
14699
14930
|
const expired = await this.trackStore.expireStale(Date.now());
|
|
14700
14931
|
for (const t of expired) {
|
|
14701
14932
|
const duration = t.lastSeen - t.firstSeen;
|
|
14933
|
+
const closure = this.trackClosureState.get(t.trackId);
|
|
14934
|
+
const outcome = decideZeroMediaPolicy({
|
|
14935
|
+
durationMs: duration,
|
|
14936
|
+
hasMedia: (await this.mediaStore?.listByOwner("track", t.trackId) ?? []).length > 0,
|
|
14937
|
+
confirmed: closure?.confirmed ?? false,
|
|
14938
|
+
hasRasterFallback: closure?.rasterFallback !== void 0
|
|
14939
|
+
});
|
|
14940
|
+
if (outcome === "suppress") {
|
|
14941
|
+
this.ctx.logger.info("track suppressed (zero-media false birth)", {
|
|
14942
|
+
tags: { deviceId: t.deviceId },
|
|
14943
|
+
meta: {
|
|
14944
|
+
trackId: t.trackId,
|
|
14945
|
+
className: t.className,
|
|
14946
|
+
durationMs: duration,
|
|
14947
|
+
positions: t.positions.length
|
|
14948
|
+
}
|
|
14949
|
+
});
|
|
14950
|
+
await this.suppressZeroMediaTrack(t.deviceId, t.trackId);
|
|
14951
|
+
continue;
|
|
14952
|
+
}
|
|
14953
|
+
if (outcome === "raster-fallback" && closure?.rasterFallback) try {
|
|
14954
|
+
await this.mediaStore?.put({
|
|
14955
|
+
deviceId: t.deviceId,
|
|
14956
|
+
ownerKind: "track",
|
|
14957
|
+
ownerId: t.trackId,
|
|
14958
|
+
kind: "thumbnail",
|
|
14959
|
+
timestamp: closure.rasterFallback.timestamp,
|
|
14960
|
+
data: closure.rasterFallback.jpeg
|
|
14961
|
+
});
|
|
14962
|
+
} catch (err) {
|
|
14963
|
+
this.ctx.logger.debug("zero-media raster fallback put failed", {
|
|
14964
|
+
tags: { deviceId: t.deviceId },
|
|
14965
|
+
meta: {
|
|
14966
|
+
trackId: t.trackId,
|
|
14967
|
+
error: String(err)
|
|
14968
|
+
}
|
|
14969
|
+
});
|
|
14970
|
+
}
|
|
14702
14971
|
this.ctx.logger.info("track ended", {
|
|
14703
14972
|
tags: { deviceId: t.deviceId },
|
|
14704
14973
|
meta: {
|
|
@@ -14756,6 +15025,9 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
14756
15025
|
this.bestFrameTracker.delete(t.trackId);
|
|
14757
15026
|
this.objectEmbeddingBestSelector.delete(t.trackId);
|
|
14758
15027
|
this.lastFrameAtByTrack.delete(t.trackId);
|
|
15028
|
+
this.lastFrameInFlight.delete(t.trackId);
|
|
15029
|
+
this.firstFramePendingTracks.delete(t.trackId);
|
|
15030
|
+
this.firstFrameInFlight.delete(t.trackId);
|
|
14759
15031
|
this.thumbnailLandedTracks.delete(t.trackId);
|
|
14760
15032
|
this.thumbnailInFlight.delete(t.trackId);
|
|
14761
15033
|
this.keyFrameInFlight.delete(t.trackId);
|
|
@@ -14809,12 +15081,56 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
14809
15081
|
});
|
|
14810
15082
|
this.emitTrackLifecycle(endPayload, t.lastSeen);
|
|
14811
15083
|
this.trackLifecycleUpdateMem.delete(t.trackId);
|
|
15084
|
+
this.trackClosureState.delete(t.trackId);
|
|
14812
15085
|
}
|
|
14813
15086
|
} catch (err) {
|
|
14814
15087
|
if (this.shuttingDown) return;
|
|
14815
15088
|
this.ctx.logger.debug("sweepExpiredTracks failed", { meta: { error: String(err) } });
|
|
14816
15089
|
}
|
|
14817
15090
|
}
|
|
15091
|
+
/**
|
|
15092
|
+
* Undo a zero-media false-birth track (spec §"Zero-media track policy"): tear
|
|
15093
|
+
* down its live per-track state (mirroring the natural end MINUS importance
|
|
15094
|
+
* scoring + the lifecycle `end`/`TrackEnded` events) and cascade-delete the
|
|
15095
|
+
* just-persisted row + any events/media so nothing dangles. Best-effort.
|
|
15096
|
+
*/
|
|
15097
|
+
async suppressZeroMediaTrack(deviceId, trackId) {
|
|
15098
|
+
const faceEnd = this.faceRecognizer?.onTrackEnd(deviceId, trackId);
|
|
15099
|
+
const dropKeyFrame = () => {
|
|
15100
|
+
this.keyFrameKeyByTrackId.delete(trackId);
|
|
15101
|
+
};
|
|
15102
|
+
if (faceEnd) faceEnd.finally(dropKeyFrame);
|
|
15103
|
+
else dropKeyFrame();
|
|
15104
|
+
this.plateRecognizer?.onTrackEnd(deviceId, trackId);
|
|
15105
|
+
this.bestFrameTracker.delete(trackId);
|
|
15106
|
+
this.objectEmbeddingBestSelector.delete(trackId);
|
|
15107
|
+
this.lastFrameAtByTrack.delete(trackId);
|
|
15108
|
+
this.lastFrameInFlight.delete(trackId);
|
|
15109
|
+
this.firstFramePendingTracks.delete(trackId);
|
|
15110
|
+
this.firstFrameInFlight.delete(trackId);
|
|
15111
|
+
this.thumbnailLandedTracks.delete(trackId);
|
|
15112
|
+
this.thumbnailInFlight.delete(trackId);
|
|
15113
|
+
this.keyFrameInFlight.delete(trackId);
|
|
15114
|
+
this.detailDispatcher?.onTrackEnded(deviceId, trackId);
|
|
15115
|
+
this.overlayState.onTrackEnded(deviceId, trackId);
|
|
15116
|
+
this.trackLifecycleUpdateMem.delete(trackId);
|
|
15117
|
+
this.trackClosureState.delete(trackId);
|
|
15118
|
+
if (this.eventStore && this.mediaStore && this.trackStore) try {
|
|
15119
|
+
await cascadeDeleteTrack({
|
|
15120
|
+
eventStore: this.eventStore,
|
|
15121
|
+
mediaStore: this.mediaStore,
|
|
15122
|
+
trackStore: this.trackStore
|
|
15123
|
+
}, trackId);
|
|
15124
|
+
} catch (err) {
|
|
15125
|
+
this.ctx.logger.debug("zero-media suppression cascade failed", {
|
|
15126
|
+
tags: { deviceId },
|
|
15127
|
+
meta: {
|
|
15128
|
+
trackId,
|
|
15129
|
+
error: String(err)
|
|
15130
|
+
}
|
|
15131
|
+
});
|
|
15132
|
+
}
|
|
15133
|
+
}
|
|
14818
15134
|
async sweepRetention() {
|
|
14819
15135
|
if (this.shuttingDown || !this.eventStore || !this.mediaStore) return;
|
|
14820
15136
|
const now = Date.now();
|
|
@@ -15020,10 +15336,14 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
15020
15336
|
this.bestFrameTracker.delete(track.trackId);
|
|
15021
15337
|
this.objectEmbeddingBestSelector.delete(track.trackId);
|
|
15022
15338
|
this.lastFrameAtByTrack.delete(track.trackId);
|
|
15339
|
+
this.lastFrameInFlight.delete(track.trackId);
|
|
15340
|
+
this.firstFramePendingTracks.delete(track.trackId);
|
|
15341
|
+
this.firstFrameInFlight.delete(track.trackId);
|
|
15023
15342
|
this.thumbnailLandedTracks.delete(track.trackId);
|
|
15024
15343
|
this.thumbnailInFlight.delete(track.trackId);
|
|
15025
15344
|
this.keyFrameInFlight.delete(track.trackId);
|
|
15026
15345
|
this.trackLifecycleUpdateMem.delete(track.trackId);
|
|
15346
|
+
this.trackClosureState.delete(track.trackId);
|
|
15027
15347
|
this.detailDispatcher?.onTrackEnded(deviceId, track.trackId);
|
|
15028
15348
|
this.overlayState.onTrackEnded(deviceId, track.trackId);
|
|
15029
15349
|
this.lastActiveTrackIds.get(key)?.delete(track.trackId);
|
|
@@ -15322,6 +15642,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
15322
15642
|
}
|
|
15323
15643
|
async clearTracks(input) {
|
|
15324
15644
|
this.trackStore?.clearDevice(input.deviceId);
|
|
15645
|
+
this.clearClosureStateForDevice(input.deviceId);
|
|
15325
15646
|
this.stationaryRegistry?.clearDevice(input.deviceId);
|
|
15326
15647
|
this.overlayState.clearDevice(input.deviceId);
|
|
15327
15648
|
this.overlaySynthesisWarnAt.delete(input.deviceId);
|
|
@@ -15377,7 +15698,9 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
15377
15698
|
return input.kind ? all.filter((m) => m.kind === input.kind) : all;
|
|
15378
15699
|
}
|
|
15379
15700
|
async getTrackMedia(input) {
|
|
15380
|
-
|
|
15701
|
+
const all = await (this.mediaStore?.listByOwner("track", input.trackId) ?? Promise.resolve([]));
|
|
15702
|
+
const kinds = input.kinds;
|
|
15703
|
+
return kinds && kinds.length > 0 ? all.filter((m) => kinds.includes(m.kind)) : all;
|
|
15381
15704
|
}
|
|
15382
15705
|
/**
|
|
15383
15706
|
* Search object events by text using CLIP cosine similarity.
|
|
@@ -15534,6 +15857,27 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
15534
15857
|
clearLiveTrackState(deviceId, trackId) {
|
|
15535
15858
|
this.detailDispatcher?.onTrackEnded(deviceId, trackId);
|
|
15536
15859
|
this.overlayState.onTrackEnded(deviceId, trackId);
|
|
15860
|
+
this.trackClosureState.delete(trackId);
|
|
15861
|
+
}
|
|
15862
|
+
/** Lazily create + return a track's close-policy state (zero-media policy). */
|
|
15863
|
+
ensureTrackClosureState(deviceId, trackId) {
|
|
15864
|
+
let st = this.trackClosureState.get(trackId);
|
|
15865
|
+
if (!st) {
|
|
15866
|
+
st = {
|
|
15867
|
+
deviceId,
|
|
15868
|
+
confirmed: false
|
|
15869
|
+
};
|
|
15870
|
+
this.trackClosureState.set(trackId, st);
|
|
15871
|
+
}
|
|
15872
|
+
return st;
|
|
15873
|
+
}
|
|
15874
|
+
/** Sticky-mark a track as confirmed once it reaches the tracker's hit gate. */
|
|
15875
|
+
markTrackConfirmed(deviceId, trackId) {
|
|
15876
|
+
this.ensureTrackClosureState(deviceId, trackId).confirmed = true;
|
|
15877
|
+
}
|
|
15878
|
+
/** Drop every close-policy entry for a device (mirrors `trackStore.clearDevice`). */
|
|
15879
|
+
clearClosureStateForDevice(deviceId) {
|
|
15880
|
+
for (const [trackId, st] of this.trackClosureState) if (st.deviceId === deviceId) this.trackClosureState.delete(trackId);
|
|
15537
15881
|
}
|
|
15538
15882
|
async deleteTracks(input) {
|
|
15539
15883
|
const cascade = this.buildTrackCascadeRegistry();
|
|
@@ -15828,11 +16172,11 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
15828
16172
|
const eventFiles = await (this.mediaStore?.listByOwner("event", id) ?? Promise.resolve([]));
|
|
15829
16173
|
if (preferKind !== void 0 && preferKind.length > 0) {
|
|
15830
16174
|
const trackFiles = await (this.mediaStore?.listByOwner("track", id) ?? Promise.resolve([]));
|
|
15831
|
-
const
|
|
15832
|
-
if (!
|
|
16175
|
+
const chosen = resolveCropForcedMedia([...eventFiles, ...trackFiles], (files) => pickCleanMedia(files, preferKind));
|
|
16176
|
+
if (!chosen) return null;
|
|
15833
16177
|
return {
|
|
15834
|
-
bytes: Buffer.from(
|
|
15835
|
-
key:
|
|
16178
|
+
bytes: Buffer.from(chosen.base64, "base64"),
|
|
16179
|
+
key: chosen.key
|
|
15836
16180
|
};
|
|
15837
16181
|
}
|
|
15838
16182
|
const chosen = await resolveDefaultEventMedia({
|