@camstack/addon-post-analysis 1.2.315 → 1.2.319
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/{clip-model-registry-8Ss3XHG0.js → clip-model-registry-BjYpZuuI.js} +33 -7
- package/dist/{clip-model-registry-60cLfjwb.mjs → clip-model-registry-DSqobJfx.mjs} +28 -8
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BoFERv6x.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CKHzDA_B.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Bz37odzX.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-V7A5cH1T.mjs → hostInit-DDDjJDA6.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +565 -85
- package/dist/pipeline-analytics/index.mjs +565 -85
- 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-BejCg41J.mjs +0 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as NC_ALARM_SYSTEM_EVENT_KINDS, $n as
|
|
1
|
+
import { $ as NC_ALARM_SYSTEM_EVENT_KINDS, $n as notificationRulesCapability, $r as object, $t as SCENE_DEFAULT_UNCOVERED_POLICY, A as EMBEDDING_REQUIREMENTS_FAILED, Ar as canonicalHash, At as NcScheduleSchema, B as ListTrackRelationsInputSchema, Bn as isAudioClassName, Br as hydrateSchema, Bt as OPS_LOG_DEFAULT_LIMIT, C as DEFAULT_EVENT_COLOR, Cn as buildEventKindDescriptor, Cr as systemEventSummaryScheduleOf, Ct as NcConditionDescriptorSchema, D as DeclaredDevices, Dn as describeSystemEventDeliveryProblem, Dr as vectorDimFromBase64, Dt as NcRuleSchema, E as DETECTION_PIPELINE_CAP_NAME, En as deriveRecordingMode, Er as templateVarsFor, Et as NcRulePatchSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as evictionPolicyOfLocation, Fr as BaseAddon, Ft as NcSystemEventKindSchema, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as isRetiredClearKey, Gr as ZodObject, Gt as RebuildObjectEmbeddingsRefusalSchema, H as LockStateSchema, Hn as isEventOwnerType, Hr as nodePin, I as FIRST_LEVEL_MACRO_CLASSES, In as faceGalleryCapability, Ir as CamProfileSchema, It as NcTaxonomySchema, J as MOTION_CLOSE_AFTER_MS, Jn as isSystemEventSummaryRule, Jr as boolean, Jt as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, K as MAX_TRACK_PURITIES, Kn as isScheduleActive, Kr as _enum, Kt as RetrainStatusSchema, L as FULL_IMAGE_BBOX, Ln as failureContributionCapability, Lr as createEvent, Lt as NcTemplatePreviewInputSchema, M as EVENT_DENSITY_BATCH_MAX, Mr as isSourceCap, Mt as NcSnoozeSchema, N as EVENT_KIND_BY_CAP, Nr as DeviceType, Nt as NcSnoozeSuppressedSchema, O as DetectionSourceSchema, On as describeSystemEventFamilyProblem, Or as zoneAnalyticsCapability, Ot as NcRuleTargetSchema, P as EVENT_OWNER_TYPES, Pn as encodeVectorBase64, Pr as errMsg, Pt as NcStateSummarySectionIdSchema, Q as NC_ALARM_DELAY_MAX_SEC, Qn as memoizePinnedFfmpeg, Qr as number, Qt as SCENE_DEFAULT_QUIET_SECONDS, R as FailureCounters, Rn as gifDeriveSpeed, Rr as customAction, Rt as NcTemplatePreviewSchema, S as CoverStateSchema, Sn as audioModeOf, Sr as systemEventFilterApplies, St as NcAlarmSettingsPatchSchema, T as DETECTION_MACRO_CLASSES, Tn as cosineSimilarity$1, Tr as templateCarriesSummary, Tt as NcRuleInputSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Un as isKnownTemplateVar, Ut as RECORDING_EXPORT_MAX_READ_BYTES, V as ListTrackRelationsResultSchema, Vn as isDetectionMacroClass, Vr as isDeviceScopedCap, Vt as OpsLogEntrySchema, W as MAX_BIRTH_DECISION_RECORDS, Wn as isMediaOwnerType, Wr as sleep$1, Wt as RECORDING_SOURCE_CAMSTACK_ADDON, X as MotionTriggeredBySchema, Xn as kebabToCamel, Xr as literal, Xt as SCENE_DEFAULT_CHECK_INTERVAL_SEC, Y as MediaFileKindEnum, Yn as isVectorIndexDimConflict, Yr as discriminatedUnion, Yt as SCENE_DEFAULT_ANCHOR_THRESHOLD, Z as NC_ALARM_DELAY_FIELDS, Zn as mayWriteToLocation, Zr as looseObject, Zt as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, _ as BaseDevice, _n as alarmPanelCapability, _r as servedTemplateVars, _t as NC_TAXONOMY, a as CLIP_EMBEDDING_STEP_ID, ai as tuple, an as SetTrackRelationInputSchema, ar as readMotionTriggeredBy, at as NC_CLIP_RETENTION_MAX_SEC, b as ClearTrackRelationInputSchema, bn as assertTimelapseCadences, br as systemEventDeliveryProblems, bt as NcAlarmSensorsInputSchema, c as resolveClusterModelPin, ci as EventCategory, cn as TimelapseRuleInputSchema, cr as resolveLocationMode, ct as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, dn as TrackPuritySchema, dr as ruleBelongsToAlarm$1, dt as NC_GROUPING_MAX_SEC, ei as partialRecord, en as SCENE_DIVERGED, er as occupancyScope, et as NC_ALARM_TRIGGERED_MAX_SEC, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fn as TrackRelationKindSchema, fr as ruleDeviceCompatCapQueries, ft as NC_GROUPING_MIN_SEC, g as BIRTH_DECISION_DEFAULT_LIMIT, gn as addonWidgetsSourceCapability, gr as sensorRaisedOf, gt as NC_TANK_SYSTEM_EVENT_KINDS, h as ArchivedDebugNoteSchema, hn as TrackSourceSchema, hr as sceneMonitorCapability, ht as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, ii as templateLiteral, in as SetTrackPurityResultSchema, ir as readDeviceStateFrom, it as NC_CLIP_PRE_ROLL_DEFAULT_SEC, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, jn as embeddingRefusalOf, jr as evaluateSensorEdge, jt as NcSnoozeInputSchema, k as EMBEDDING_MODEL_FAILED, kr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, kt as NcRuleUpdateInputSchema, l as APPEARANCE_BIN_COUNT, ln as TimelapseRulePatchSchema, lr as resolveModeDelays, lt as NC_DEFAULT_SNOOZE_MINUTES, m as AlarmArmModeSchema, mn as TrackSideSchema, mr as ruleKindOf, mt as NC_SNOOZE_MAX_MINUTES, ni as strictObject, nn as SceneMonitorSchema, nr as pipelineAnalyticsCapability, nt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, o as FACE_EMBEDDING_STEP_ID, oi as union, on as TIMELAPSE_DENSE_FLOOR_SEC, or as readTimelapseGeneratedAt, ot as NC_CONDITION_CATALOG, p as AUDIO_MACRO_LABELS, pn as TrackRelationSchema, pr as ruleGroupingApplies, pt as NC_SENSOR_TRANSITION_KINDS, q as MAX_TRACK_RELATIONS, qn as isSystemDelivery, qr as array, qt as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, ri as string, rn as SetTrackPurityInputSchema, rr as plateGalleryCapability, rt as NC_CLIP_POST_ROLL_DEFAULT_SEC, s as readClusterModelPinStrict, si as unknown, sn as TRACK_RELATIONS_DEFAULT_LIMIT, sr as resolveClipSearchMinScore, st as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, t as BUILTIN_CLIP_MODELS, ti as record, tn as SCENE_RESET_RECAPTURES, tt as NC_AUDIO_CONFIRM_HITS_DEFAULT, u as APPEARANCE_BLOCKS, un as TimelapseRuleSchema, ut as NC_GROUPING_DEFAULT_SEC, v as BirthDecisionRecordSchema, vn as applyModeDelaysEdits, vr as storageOccupancyCapability, vt as NC_TEMPLATE_VARS, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wn as consumableLowPercentOf, wr as systemEventSummarySplitOf, wt as NcConditionsSchema, x as ClearTrackRelationResultSchema, xn as audioMetricsCapability, xr as systemEventFamilyProblems, xt as NcAlarmSensorsSchema, y as COCO_TO_MACRO, yn as applyNonBlockingEdit, yr as subKindsOf, yt as NcAlarmConfigSchema, z as LabelAttributionSchema, zn as gifFromMp4Args, zr as defineCustomActions, zt as NcTemplateVarDescriptorSchema } from "../clip-model-registry-DSqobJfx.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs$1 from "node:fs";
|
|
4
4
|
import { createWriteStream, promises } from "node:fs";
|
|
@@ -16988,7 +16988,7 @@ var NcStateSummaryTestSchema = object({
|
|
|
16988
16988
|
/** Nothing open and every section read: nothing was enqueued. */
|
|
16989
16989
|
nothingToReport: boolean(),
|
|
16990
16990
|
sections: array(object({
|
|
16991
|
-
id:
|
|
16991
|
+
id: NcStateSummarySectionIdSchema,
|
|
16992
16992
|
status: _enum(["read", "unavailable"]),
|
|
16993
16993
|
items: number().int().nonnegative(),
|
|
16994
16994
|
reason: string().optional()
|
|
@@ -16996,7 +16996,7 @@ var NcStateSummaryTestSchema = object({
|
|
|
16996
16996
|
rows: array(object({
|
|
16997
16997
|
recordId: string(),
|
|
16998
16998
|
/** The section of a per-section row. */
|
|
16999
|
-
section:
|
|
16999
|
+
section: NcStateSummarySectionIdSchema.optional(),
|
|
17000
17000
|
enqueued: number().int().nonnegative(),
|
|
17001
17001
|
targetIds: array(string())
|
|
17002
17002
|
}))
|
|
@@ -20707,17 +20707,8 @@ var NcDeviceStateCache = class {
|
|
|
20707
20707
|
* a job that COMPLETED, and the state it leaves behind is not a condition that
|
|
20708
20708
|
* is still true. The ADR records why it is excluded rather than approximated.
|
|
20709
20709
|
*/
|
|
20710
|
-
|
|
20711
|
-
|
|
20712
|
-
"streams",
|
|
20713
|
-
"battery",
|
|
20714
|
-
"consumables",
|
|
20715
|
-
"tanks",
|
|
20716
|
-
"detection-blind",
|
|
20717
|
-
"nodes",
|
|
20718
|
-
"addons",
|
|
20719
|
-
"updates"
|
|
20720
|
-
];
|
|
20710
|
+
/** One source: the cap's Zod enum (D698 LOW-6). */
|
|
20711
|
+
var NC_STATE_SUMMARY_SECTION_IDS = NcStateSummarySectionIdSchema.options;
|
|
20721
20712
|
/** The kind groups that have no section, and why — said on the preview. */
|
|
20722
20713
|
var NC_STATE_SUMMARY_EXCLUDED_GROUPS = { "backups-exports": "a backup or an export is a completed job, not a state that is still true" };
|
|
20723
20714
|
function isStateSectionId(value) {
|
|
@@ -35186,10 +35177,10 @@ var NcStateSummaryService = class {
|
|
|
35186
35177
|
const rows = [];
|
|
35187
35178
|
for (const subject of subjects) {
|
|
35188
35179
|
const enqueued = await this.deps.enqueue(rule, subject, "synthetic");
|
|
35189
|
-
const section = subject
|
|
35180
|
+
const section = stateSummarySectionOf(subject);
|
|
35190
35181
|
rows.push({
|
|
35191
35182
|
recordId: subject.recordId,
|
|
35192
|
-
...
|
|
35183
|
+
...section !== void 0 ? { section } : {},
|
|
35193
35184
|
enqueued: enqueued.inserted,
|
|
35194
35185
|
targetIds: [...enqueued.targetIds]
|
|
35195
35186
|
});
|
|
@@ -51329,6 +51320,11 @@ var MEDIA_ID_SEGMENT = "m";
|
|
|
51329
51320
|
/** Path segment that introduces an OWNER question ("best media of"). */
|
|
51330
51321
|
var MEDIA_OWNER_SEGMENT = "o";
|
|
51331
51322
|
/**
|
|
51323
|
+
* Path segment that introduces an event-owner GENERATION (D694) —
|
|
51324
|
+
* `/g/<deviceId>/<recordAt>` after the owner id.
|
|
51325
|
+
*/
|
|
51326
|
+
var MEDIA_GENERATION_SEGMENT = "g";
|
|
51327
|
+
/**
|
|
51332
51328
|
* The plane path for one media row.
|
|
51333
51329
|
*
|
|
51334
51330
|
* `prefix` is `/addon/<addonId>/event-media` — a pure function of the addon
|
|
@@ -51345,6 +51341,23 @@ function mediaFileUrl(prefix, mediaId) {
|
|
|
51345
51341
|
return `${prefix}/${MEDIA_ID_SEGMENT}/${mediaId}`;
|
|
51346
51342
|
}
|
|
51347
51343
|
/**
|
|
51344
|
+
* The plane path for "whatever media best represents this owner".
|
|
51345
|
+
*
|
|
51346
|
+
* The owner id IS encoded: a trackId is a UUID today but an owner id is an
|
|
51347
|
+
* arbitrary row key (the summary mosaic's is `summary-<ruleId>-<windowEndMs>`),
|
|
51348
|
+
* and one slash in it would silently re-cut the path.
|
|
51349
|
+
*
|
|
51350
|
+
* When `generation` is set and `ownerType` is an event table, the path carries
|
|
51351
|
+
* `/g/<deviceId>/<recordAt>` so a reused rowid cannot resolve another record's
|
|
51352
|
+
* crop. Generation is ignored (and not minted) for non-event owners — a track
|
|
51353
|
+
* id is random and needs none.
|
|
51354
|
+
*/
|
|
51355
|
+
function mediaOwnerUrl(prefix, ownerType, ownerId, generation) {
|
|
51356
|
+
const base = `${prefix}/${MEDIA_OWNER_SEGMENT}/${ownerType}/${encodeURIComponent(ownerId)}`;
|
|
51357
|
+
if (generation === void 0 || !isEventOwnerType(ownerType)) return base;
|
|
51358
|
+
return `${base}/${MEDIA_GENERATION_SEGMENT}/${generation.deviceId}/${generation.recordAt}`;
|
|
51359
|
+
}
|
|
51360
|
+
/**
|
|
51348
51361
|
* A stored row as the wire sees it: the ref, the address, and — transitionally
|
|
51349
51362
|
* — the bytes.
|
|
51350
51363
|
*
|
|
@@ -59195,6 +59208,12 @@ function openGzipStream(out) {
|
|
|
59195
59208
|
onceDrain: (listener) => {
|
|
59196
59209
|
gzip.once("drain", listener);
|
|
59197
59210
|
},
|
|
59211
|
+
flush: () => new Promise((resolve, reject) => {
|
|
59212
|
+
gzip.flush((err) => {
|
|
59213
|
+
if (err !== null && err !== void 0) reject(err);
|
|
59214
|
+
else resolve();
|
|
59215
|
+
});
|
|
59216
|
+
}),
|
|
59198
59217
|
finish: async () => {
|
|
59199
59218
|
gzip.end();
|
|
59200
59219
|
await closed;
|
|
@@ -59290,6 +59309,25 @@ var TapeSegmentWriter = class {
|
|
|
59290
59309
|
});
|
|
59291
59310
|
}
|
|
59292
59311
|
/**
|
|
59312
|
+
* Push the sink's pending gzip output to disk without ending the segment.
|
|
59313
|
+
* A quiet / silent camera otherwise keeps its tail only in the zlib
|
|
59314
|
+
* buffer until the next rotation (D681 follow-up). Never throws into the
|
|
59315
|
+
* caller's frame path: a flush fault is logged and swallowed here.
|
|
59316
|
+
*/
|
|
59317
|
+
async flushToDisk() {
|
|
59318
|
+
try {
|
|
59319
|
+
await this.deps.sink.flush();
|
|
59320
|
+
} catch (err) {
|
|
59321
|
+
this.deps.logger.warn("tracking tape segment flush failed", {
|
|
59322
|
+
tags: { deviceId: this.deps.deviceId },
|
|
59323
|
+
meta: {
|
|
59324
|
+
segmentOriginMs: this.deps.segmentOriginMs,
|
|
59325
|
+
error: errMsg(err)
|
|
59326
|
+
}
|
|
59327
|
+
});
|
|
59328
|
+
}
|
|
59329
|
+
}
|
|
59330
|
+
/**
|
|
59293
59331
|
* Always ends the segment's records (queue flush, lost-span gap, `end`
|
|
59294
59332
|
* record) before touching the sink, so a sink that cannot finish still
|
|
59295
59333
|
* leaves a decodable body behind it. `sink.finish()`'s rejection is caught
|
|
@@ -59616,6 +59654,21 @@ var TapeRecorder = class {
|
|
|
59616
59654
|
if (cam !== void 0) await this.closeSegment(deviceId, cam, "segment-end");
|
|
59617
59655
|
}
|
|
59618
59656
|
/**
|
|
59657
|
+
* Push every open segment's pending gzip output to disk without rotating
|
|
59658
|
+
* (D681 follow-up). A silent / quiet camera otherwise keeps its tail only
|
|
59659
|
+
* in the zlib buffer until the next frame or the 10-minute cut — a crash
|
|
59660
|
+
* loses it, and `bytesLastHour` lags behind what was already recorded.
|
|
59661
|
+
* Never throws: a per-camera flush fault is logged inside the writer.
|
|
59662
|
+
*/
|
|
59663
|
+
async flushOpenSegments() {
|
|
59664
|
+
const pending = [];
|
|
59665
|
+
for (const cam of this.cameras.values()) {
|
|
59666
|
+
const writer = cam.segment?.writer;
|
|
59667
|
+
if (writer !== void 0 && writer !== null) pending.push(writer.flushToDisk());
|
|
59668
|
+
}
|
|
59669
|
+
await Promise.all(pending);
|
|
59670
|
+
}
|
|
59671
|
+
/**
|
|
59619
59672
|
* Close the camera's open segment only if it holds time at or before
|
|
59620
59673
|
* `atMs` — what a promotion of a window ending at `atMs` needs on disk
|
|
59621
59674
|
* (Task 12 fix round, H1). A window wholly before the open segment leaves
|
|
@@ -60115,6 +60168,29 @@ var TapeShelf = class {
|
|
|
60115
60168
|
};
|
|
60116
60169
|
}
|
|
60117
60170
|
};
|
|
60171
|
+
/**
|
|
60172
|
+
* Log every refusal the sweep already measured (D391 / D681 follow-up).
|
|
60173
|
+
* `sweep()` itself never throws for `rootUnreadable` / per-camera `failed`:
|
|
60174
|
+
* those land on the report, and a caller that only `.catch`es would drop them
|
|
60175
|
+
* silently even when the shelf already warned at the `readdir` site. One
|
|
60176
|
+
* tagged line per refusal at the sweep boundary closes that gap.
|
|
60177
|
+
*/
|
|
60178
|
+
function logTapeSweepReport(report, logger) {
|
|
60179
|
+
if (report.rootUnreadable !== null) logger.warn("tracking tape sweep refused", { meta: {
|
|
60180
|
+
reason: report.rootUnreadable,
|
|
60181
|
+
scope: "root"
|
|
60182
|
+
} });
|
|
60183
|
+
for (const [deviceId, outcome] of report.perDevice) {
|
|
60184
|
+
if (!("failed" in outcome) || outcome.failed !== true) continue;
|
|
60185
|
+
logger.warn("tracking tape sweep refused", {
|
|
60186
|
+
tags: { deviceId },
|
|
60187
|
+
meta: {
|
|
60188
|
+
reason: outcome.reason,
|
|
60189
|
+
scope: "camera"
|
|
60190
|
+
}
|
|
60191
|
+
});
|
|
60192
|
+
}
|
|
60193
|
+
}
|
|
60118
60194
|
//#endregion
|
|
60119
60195
|
//#region src/pipeline-analytics/tracking-bench/recorder/tape-operation-queue.ts
|
|
60120
60196
|
/** Generous: a promotion reads its window's segments and walks back through
|
|
@@ -61168,6 +61244,17 @@ function shelfFigures(listing, now) {
|
|
|
61168
61244
|
...last !== void 0 ? { heldToMs: last.originMs } : {}
|
|
61169
61245
|
};
|
|
61170
61246
|
}
|
|
61247
|
+
/**
|
|
61248
|
+
* Default `debug.tape.status` (no `deviceIds`): drop the noise. An Off camera
|
|
61249
|
+
* with a measured empty shelf is not a cost row and is not promotable; keep
|
|
61250
|
+
* everything else — Recording / Refused / Failing / Unknown, Off with held
|
|
61251
|
+
* segments (still promotable after the switch went off), and Off whose shelf
|
|
61252
|
+
* could not be read (D393: never silently hide an unreadable camera). Explicit
|
|
61253
|
+
* `deviceIds` still return every asked-about camera, Off included (D62).
|
|
61254
|
+
*/
|
|
61255
|
+
function defaultTapeStatusCameras(rows) {
|
|
61256
|
+
return rows.filter((row) => row.state !== "off" || row.segmentsHeld === null || row.segmentsHeld > 0);
|
|
61257
|
+
}
|
|
61171
61258
|
function describeTapeStatus(input) {
|
|
61172
61259
|
return input.deviceIds.map((deviceId) => {
|
|
61173
61260
|
const shelf = {
|
|
@@ -66078,6 +66165,51 @@ var EventStore = class {
|
|
|
66078
66165
|
return found;
|
|
66079
66166
|
}
|
|
66080
66167
|
/**
|
|
66168
|
+
* The GENERATION of each live event in `table` among `eventIds` — its camera
|
|
66169
|
+
* and its own timestamp (D694). Same throw-on-failure contract as
|
|
66170
|
+
* {@link existingEventIdsIn}: a quiet under-report here would let the orphan
|
|
66171
|
+
* audit keep treating another camera's leftovers as owned.
|
|
66172
|
+
*/
|
|
66173
|
+
async eventGenerationsIn(table, eventIds) {
|
|
66174
|
+
const collection = EVENT_COLLECTION_BY_TABLE[table];
|
|
66175
|
+
const found = /* @__PURE__ */ new Map();
|
|
66176
|
+
for (let i = 0; i < eventIds.length; i += PRUNE_PAGE_SIZE) {
|
|
66177
|
+
const chunk = eventIds.slice(i, i + PRUNE_PAGE_SIZE);
|
|
66178
|
+
if (chunk.length === 0) continue;
|
|
66179
|
+
const rows = await this.store.query.query({
|
|
66180
|
+
collection,
|
|
66181
|
+
columns: [
|
|
66182
|
+
"id",
|
|
66183
|
+
"deviceId",
|
|
66184
|
+
"timestamp"
|
|
66185
|
+
],
|
|
66186
|
+
filter: {
|
|
66187
|
+
whereIn: { id: [...chunk] },
|
|
66188
|
+
limit: chunk.length
|
|
66189
|
+
}
|
|
66190
|
+
});
|
|
66191
|
+
for (const row of rows) {
|
|
66192
|
+
const deviceId = row.data["deviceId"];
|
|
66193
|
+
const timestamp = row.data["timestamp"];
|
|
66194
|
+
if (typeof deviceId !== "number" || !Number.isFinite(deviceId)) continue;
|
|
66195
|
+
if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) continue;
|
|
66196
|
+
found.set(row.id, {
|
|
66197
|
+
deviceId,
|
|
66198
|
+
timestamp
|
|
66199
|
+
});
|
|
66200
|
+
}
|
|
66201
|
+
}
|
|
66202
|
+
return found;
|
|
66203
|
+
}
|
|
66204
|
+
/**
|
|
66205
|
+
* One event's generation, or `null` when the id is not live in `table`.
|
|
66206
|
+
* Used by the `/o/<eventOwner>/<id>` plane ladder to scope media to the
|
|
66207
|
+
* current holder (D694).
|
|
66208
|
+
*/
|
|
66209
|
+
async eventGenerationOf(table, eventId) {
|
|
66210
|
+
return (await this.eventGenerationsIn(table, [eventId])).get(eventId) ?? null;
|
|
66211
|
+
}
|
|
66212
|
+
/**
|
|
66081
66213
|
* The SUBSET of `eventIds` that are MOTION events specifically.
|
|
66082
66214
|
*
|
|
66083
66215
|
* {@link existingEventIds} answers "is this event alive", which is the
|
|
@@ -72981,7 +73113,7 @@ var AnalyticsQueryFacade = class {
|
|
|
72981
73113
|
async getMotionEvents(input) {
|
|
72982
73114
|
const events = await (this.deps.eventStore()?.queryMotion(input) ?? Promise.resolve([]));
|
|
72983
73115
|
if (input.projection !== "slim" || this.deps.eventMediaBaseUrl() === null) return events;
|
|
72984
|
-
return this.withMediaUrl(events);
|
|
73116
|
+
return this.withMediaUrl(events, "motion");
|
|
72985
73117
|
}
|
|
72986
73118
|
async getEventDensity(input) {
|
|
72987
73119
|
return this.deps.eventStore()?.densityByKind(input) ?? [];
|
|
@@ -73038,12 +73170,12 @@ var AnalyticsQueryFacade = class {
|
|
|
73038
73170
|
async getObjectEvents(input) {
|
|
73039
73171
|
const events = await (this.deps.eventStore()?.queryObject(input) ?? Promise.resolve([]));
|
|
73040
73172
|
if (input.projection !== "slim" || this.deps.eventMediaBaseUrl() === null) return events;
|
|
73041
|
-
return this.withMediaUrl(events);
|
|
73173
|
+
return this.withMediaUrl(events, "object");
|
|
73042
73174
|
}
|
|
73043
73175
|
async getAudioEvents(input) {
|
|
73044
73176
|
const events = await (this.deps.eventStore()?.queryAudio(input) ?? Promise.resolve([]));
|
|
73045
73177
|
if (input.projection !== "slim" || this.deps.eventMediaBaseUrl() === null) return events;
|
|
73046
|
-
return this.withMediaUrl(events);
|
|
73178
|
+
return this.withMediaUrl(events, "audio");
|
|
73047
73179
|
}
|
|
73048
73180
|
async getEventMedia(input) {
|
|
73049
73181
|
const store = this.deps.mediaStore();
|
|
@@ -73310,13 +73442,20 @@ var AnalyticsQueryFacade = class {
|
|
|
73310
73442
|
*
|
|
73311
73443
|
* Only called when `eventMediaBaseUrl()` is non-null (guard sits in
|
|
73312
73444
|
* the callers), so the template literal is safe.
|
|
73445
|
+
*
|
|
73446
|
+
* The URL names the owner TABLE and the event's GENERATION (D482 + D694):
|
|
73447
|
+
* a bare `${base}/${id}` was both ambiguous across tables and unable to
|
|
73448
|
+
* survive a reused rowid. New stamps always carry `/o/<type>/<id>/g/…`.
|
|
73313
73449
|
*/
|
|
73314
|
-
withMediaUrl(events) {
|
|
73450
|
+
withMediaUrl(events, ownerType) {
|
|
73315
73451
|
const base = this.deps.eventMediaBaseUrl();
|
|
73316
73452
|
if (base === null) return events;
|
|
73317
73453
|
return events.map((e) => ({
|
|
73318
73454
|
...e,
|
|
73319
|
-
mediaUrl:
|
|
73455
|
+
mediaUrl: mediaOwnerUrl(base, ownerType, String(e.id), {
|
|
73456
|
+
deviceId: e.deviceId,
|
|
73457
|
+
recordAt: e.timestamp
|
|
73458
|
+
})
|
|
73320
73459
|
}));
|
|
73321
73460
|
}
|
|
73322
73461
|
};
|
|
@@ -76148,18 +76287,26 @@ function eventMediaEtag(media) {
|
|
|
76148
76287
|
}
|
|
76149
76288
|
/** Digits only — `Number('')` is 0 and `Number(' ')` is 0, so neither is used. */
|
|
76150
76289
|
var DIGITS = /^\d+$/;
|
|
76290
|
+
/** One run of digits, for composing the event-owner remainder pattern. */
|
|
76291
|
+
var DIGIT_RUN = "\\d+";
|
|
76292
|
+
/**
|
|
76293
|
+
* Event-owner remainder with an optional generation suffix (D694):
|
|
76294
|
+
* `<ownerId>` or `<ownerId>/g/<deviceId>/<recordAt>`. Owner ids for event
|
|
76295
|
+
* tables are decimal rowids; the generation segment is the literal `g`.
|
|
76296
|
+
*/
|
|
76297
|
+
var EVENT_OWNER_REMAINDER = new RegExp(`^(${DIGIT_RUN})(?:/${MEDIA_GENERATION_SEGMENT}/(${DIGIT_RUN})/(${DIGIT_RUN}))?$`);
|
|
76151
76298
|
/**
|
|
76152
76299
|
* Read a plane path. `null` for anything that is not one of the two forms —
|
|
76153
76300
|
* including the BARE id the plane used to accept, which is gone: a bare number
|
|
76154
76301
|
* could be a media id or an event id, and after D474 an event id could be one
|
|
76155
76302
|
* of three rows. Ambiguity at the door is what this record removes.
|
|
76156
76303
|
*
|
|
76157
|
-
*
|
|
76158
|
-
* hub's wildcard param is percent-decoded by find-my-way before the
|
|
76159
|
-
* is proxied, so a `%2F` an owner id carried would arrive here as a
|
|
76160
|
-
* slash, and re-cutting on it would silently address a different owner.
|
|
76161
|
-
*
|
|
76162
|
-
*
|
|
76304
|
+
* For non-event owners the id is the whole remainder rather than one segment:
|
|
76305
|
+
* the hub's wildcard param is percent-decoded by find-my-way before the
|
|
76306
|
+
* sub-path is proxied, so a `%2F` an owner id carried would arrive here as a
|
|
76307
|
+
* real slash, and re-cutting on it would silently address a different owner.
|
|
76308
|
+
* Event owners (decimal rowids) may end with `/g/<deviceId>/<recordAt>` —
|
|
76309
|
+
* that suffix is the generation, not part of the id (D694).
|
|
76163
76310
|
*/
|
|
76164
76311
|
function parseMediaAddress(rawPath) {
|
|
76165
76312
|
const path = rawPath.replace(/^\/+/, "");
|
|
@@ -76179,13 +76326,35 @@ function parseMediaAddress(rawPath) {
|
|
|
76179
76326
|
const typeSlash = rest.indexOf("/");
|
|
76180
76327
|
if (typeSlash <= 0) return null;
|
|
76181
76328
|
const ownerType = rest.slice(0, typeSlash);
|
|
76182
|
-
const
|
|
76183
|
-
if (
|
|
76329
|
+
const remainder = rest.slice(typeSlash + 1);
|
|
76330
|
+
if (remainder.length === 0) return null;
|
|
76184
76331
|
if (!isMediaOwnerType(ownerType)) return null;
|
|
76332
|
+
if (isEventOwnerType(ownerType)) {
|
|
76333
|
+
const match = EVENT_OWNER_REMAINDER.exec(remainder);
|
|
76334
|
+
if (match === null) return null;
|
|
76335
|
+
const ownerId = match[1];
|
|
76336
|
+
const deviceIdRaw = match[2];
|
|
76337
|
+
const recordAtRaw = match[3];
|
|
76338
|
+
if (ownerId === void 0) return null;
|
|
76339
|
+
if (deviceIdRaw !== void 0 && recordAtRaw !== void 0) return {
|
|
76340
|
+
kind: "owner",
|
|
76341
|
+
ownerType,
|
|
76342
|
+
ownerId,
|
|
76343
|
+
generation: {
|
|
76344
|
+
deviceId: Number(deviceIdRaw),
|
|
76345
|
+
recordAt: Number(recordAtRaw)
|
|
76346
|
+
}
|
|
76347
|
+
};
|
|
76348
|
+
return {
|
|
76349
|
+
kind: "owner",
|
|
76350
|
+
ownerType,
|
|
76351
|
+
ownerId
|
|
76352
|
+
};
|
|
76353
|
+
}
|
|
76185
76354
|
return {
|
|
76186
76355
|
kind: "owner",
|
|
76187
76356
|
ownerType,
|
|
76188
|
-
ownerId
|
|
76357
|
+
ownerId: remainder
|
|
76189
76358
|
};
|
|
76190
76359
|
}
|
|
76191
76360
|
/** Set by the HUB for a `csv_*` share token admitted on this plane; never by a client. */
|
|
@@ -78862,6 +79031,218 @@ function createRetrainExportHandler(deps) {
|
|
|
78862
79031
|
};
|
|
78863
79032
|
}
|
|
78864
79033
|
//#endregion
|
|
79034
|
+
//#region src/pipeline-analytics/retrain/retrain-collection-store.ts
|
|
79035
|
+
/**
|
|
79036
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
79037
|
+
* write="one row per named training collection the operator creates. Names are
|
|
79038
|
+
* unique on nameKey (lower(trim)); empty names never become rows."
|
|
79039
|
+
* retention="none — operator-owned corpus metadata. Rows leave only when an
|
|
79040
|
+
* explicit delete API lands (not V1)."
|
|
79041
|
+
*/
|
|
79042
|
+
var RETRAIN_COLLECTIONS_COLLECTION = "pipeline-analytics:retrain-collections";
|
|
79043
|
+
var RETRAIN_COLLECTION_COLUMNS = [
|
|
79044
|
+
{
|
|
79045
|
+
name: "id",
|
|
79046
|
+
type: "TEXT",
|
|
79047
|
+
primaryKey: true,
|
|
79048
|
+
notNull: true
|
|
79049
|
+
},
|
|
79050
|
+
{
|
|
79051
|
+
name: "name",
|
|
79052
|
+
type: "TEXT",
|
|
79053
|
+
notNull: true
|
|
79054
|
+
},
|
|
79055
|
+
{
|
|
79056
|
+
name: "nameKey",
|
|
79057
|
+
type: "TEXT",
|
|
79058
|
+
notNull: true
|
|
79059
|
+
},
|
|
79060
|
+
{
|
|
79061
|
+
name: "createdAt",
|
|
79062
|
+
type: "INTEGER",
|
|
79063
|
+
notNull: true
|
|
79064
|
+
},
|
|
79065
|
+
{
|
|
79066
|
+
name: "updatedAt",
|
|
79067
|
+
type: "INTEGER",
|
|
79068
|
+
notNull: true
|
|
79069
|
+
}
|
|
79070
|
+
];
|
|
79071
|
+
var RETRAIN_COLLECTION_INDEXES = [{
|
|
79072
|
+
name: "idx_retrain_collections_name_key",
|
|
79073
|
+
columns: ["nameKey"],
|
|
79074
|
+
unique: true
|
|
79075
|
+
}, {
|
|
79076
|
+
name: "idx_retrain_collections_created",
|
|
79077
|
+
columns: ["createdAt"]
|
|
79078
|
+
}];
|
|
79079
|
+
function normalizeCollectionName(raw) {
|
|
79080
|
+
const name = raw.trim();
|
|
79081
|
+
if (name.length === 0) return null;
|
|
79082
|
+
return {
|
|
79083
|
+
name,
|
|
79084
|
+
nameKey: name.toLowerCase()
|
|
79085
|
+
};
|
|
79086
|
+
}
|
|
79087
|
+
var RetrainCollectionStore = class {
|
|
79088
|
+
store;
|
|
79089
|
+
logger;
|
|
79090
|
+
now;
|
|
79091
|
+
newId;
|
|
79092
|
+
countFrames;
|
|
79093
|
+
constructor(deps) {
|
|
79094
|
+
this.store = deps.store;
|
|
79095
|
+
this.logger = deps.logger;
|
|
79096
|
+
this.now = deps.now;
|
|
79097
|
+
this.newId = deps.newId;
|
|
79098
|
+
this.countFrames = deps.countFrames;
|
|
79099
|
+
}
|
|
79100
|
+
static async declare(store) {
|
|
79101
|
+
await store.declareCollection.mutate({
|
|
79102
|
+
collection: RETRAIN_COLLECTIONS_COLLECTION,
|
|
79103
|
+
columns: [...RETRAIN_COLLECTION_COLUMNS],
|
|
79104
|
+
indexes: [...RETRAIN_COLLECTION_INDEXES]
|
|
79105
|
+
});
|
|
79106
|
+
}
|
|
79107
|
+
async create(input) {
|
|
79108
|
+
const normalized = normalizeCollectionName(input.name);
|
|
79109
|
+
if (normalized === null) return {
|
|
79110
|
+
kind: "refused",
|
|
79111
|
+
reason: "empty-name"
|
|
79112
|
+
};
|
|
79113
|
+
if (await this.findByNameKey(normalized.nameKey) !== null) return {
|
|
79114
|
+
kind: "refused",
|
|
79115
|
+
reason: "duplicate-name"
|
|
79116
|
+
};
|
|
79117
|
+
const createdAt = this.now();
|
|
79118
|
+
const id = this.newId();
|
|
79119
|
+
await this.store.set.mutate({
|
|
79120
|
+
collection: RETRAIN_COLLECTIONS_COLLECTION,
|
|
79121
|
+
key: id,
|
|
79122
|
+
value: {
|
|
79123
|
+
name: normalized.name,
|
|
79124
|
+
nameKey: normalized.nameKey,
|
|
79125
|
+
createdAt,
|
|
79126
|
+
updatedAt: createdAt
|
|
79127
|
+
}
|
|
79128
|
+
});
|
|
79129
|
+
return {
|
|
79130
|
+
kind: "created",
|
|
79131
|
+
collection: {
|
|
79132
|
+
id,
|
|
79133
|
+
name: normalized.name,
|
|
79134
|
+
createdAt,
|
|
79135
|
+
frameCount: 0
|
|
79136
|
+
}
|
|
79137
|
+
};
|
|
79138
|
+
}
|
|
79139
|
+
async list() {
|
|
79140
|
+
let rows;
|
|
79141
|
+
try {
|
|
79142
|
+
rows = await this.store.query.query({
|
|
79143
|
+
collection: RETRAIN_COLLECTIONS_COLLECTION,
|
|
79144
|
+
filter: { orderBy: {
|
|
79145
|
+
field: "createdAt",
|
|
79146
|
+
direction: "desc"
|
|
79147
|
+
} }
|
|
79148
|
+
});
|
|
79149
|
+
} catch (err) {
|
|
79150
|
+
this.logger.warn("retrain collections list failed", { meta: { error: String(err) } });
|
|
79151
|
+
return [];
|
|
79152
|
+
}
|
|
79153
|
+
const out = [];
|
|
79154
|
+
for (const row of rows) {
|
|
79155
|
+
const name = typeof row.data["name"] === "string" ? row.data["name"] : "";
|
|
79156
|
+
const createdAt = Number(row.data["createdAt"] ?? 0);
|
|
79157
|
+
const frameCount = this.countFrames !== void 0 ? await this.countFrames(row.id) : 0;
|
|
79158
|
+
out.push({
|
|
79159
|
+
id: row.id,
|
|
79160
|
+
name,
|
|
79161
|
+
createdAt,
|
|
79162
|
+
frameCount
|
|
79163
|
+
});
|
|
79164
|
+
}
|
|
79165
|
+
return out;
|
|
79166
|
+
}
|
|
79167
|
+
async getById(id) {
|
|
79168
|
+
try {
|
|
79169
|
+
const row = await this.store.get.query({
|
|
79170
|
+
collection: RETRAIN_COLLECTIONS_COLLECTION,
|
|
79171
|
+
key: id
|
|
79172
|
+
});
|
|
79173
|
+
if (row === null || row === void 0) return null;
|
|
79174
|
+
const data = row;
|
|
79175
|
+
return {
|
|
79176
|
+
id,
|
|
79177
|
+
name: typeof data["name"] === "string" ? data["name"] : "",
|
|
79178
|
+
createdAt: Number(data["createdAt"] ?? 0),
|
|
79179
|
+
frameCount: this.countFrames !== void 0 ? await this.countFrames(id) : 0
|
|
79180
|
+
};
|
|
79181
|
+
} catch (err) {
|
|
79182
|
+
this.logger.warn("retrain collection get failed", { meta: {
|
|
79183
|
+
collectionId: id,
|
|
79184
|
+
error: String(err)
|
|
79185
|
+
} });
|
|
79186
|
+
return null;
|
|
79187
|
+
}
|
|
79188
|
+
}
|
|
79189
|
+
/**
|
|
79190
|
+
* Stub until still ingest lands. Validates the collection exists, then
|
|
79191
|
+
* refuses with a named reason so callers can wire UI against a stable shape.
|
|
79192
|
+
*/
|
|
79193
|
+
async addRetrainFrameFromRecording(input) {
|
|
79194
|
+
if (await this.getById(input.collectionId) === null) {
|
|
79195
|
+
this.logger.info("retrain collection addFrame refused: unknown-collection", {
|
|
79196
|
+
tags: { deviceId: input.deviceId },
|
|
79197
|
+
meta: {
|
|
79198
|
+
collectionId: input.collectionId,
|
|
79199
|
+
epochMs: input.epochMs
|
|
79200
|
+
}
|
|
79201
|
+
});
|
|
79202
|
+
return {
|
|
79203
|
+
kind: "refused",
|
|
79204
|
+
reason: "unknown-collection"
|
|
79205
|
+
};
|
|
79206
|
+
}
|
|
79207
|
+
this.logger.info("retrain collection addFrame stub: still-capture-unimplemented", {
|
|
79208
|
+
tags: { deviceId: input.deviceId },
|
|
79209
|
+
meta: {
|
|
79210
|
+
collectionId: input.collectionId,
|
|
79211
|
+
epochMs: input.epochMs
|
|
79212
|
+
}
|
|
79213
|
+
});
|
|
79214
|
+
return {
|
|
79215
|
+
kind: "refused",
|
|
79216
|
+
reason: "still-capture-unimplemented",
|
|
79217
|
+
detail: "Still ingest lands in plan Task 3"
|
|
79218
|
+
};
|
|
79219
|
+
}
|
|
79220
|
+
async findByNameKey(nameKey) {
|
|
79221
|
+
try {
|
|
79222
|
+
const row = (await this.store.query.query({
|
|
79223
|
+
collection: RETRAIN_COLLECTIONS_COLLECTION,
|
|
79224
|
+
filter: {
|
|
79225
|
+
where: { nameKey },
|
|
79226
|
+
limit: 1
|
|
79227
|
+
}
|
|
79228
|
+
}))[0];
|
|
79229
|
+
if (row === void 0) return null;
|
|
79230
|
+
return {
|
|
79231
|
+
id: row.id,
|
|
79232
|
+
name: typeof row.data["name"] === "string" ? row.data["name"] : "",
|
|
79233
|
+
createdAt: Number(row.data["createdAt"] ?? 0),
|
|
79234
|
+
frameCount: 0
|
|
79235
|
+
};
|
|
79236
|
+
} catch (err) {
|
|
79237
|
+
this.logger.warn("retrain collection findByNameKey failed", { meta: {
|
|
79238
|
+
nameKey,
|
|
79239
|
+
error: String(err)
|
|
79240
|
+
} });
|
|
79241
|
+
return null;
|
|
79242
|
+
}
|
|
79243
|
+
}
|
|
79244
|
+
};
|
|
79245
|
+
//#endregion
|
|
78865
79246
|
//#region src/pipeline-analytics/retrain/retrain-assist.ts
|
|
78866
79247
|
/** The whole frame, as a normalised rectangle. */
|
|
78867
79248
|
var WHOLE_FRAME = {
|
|
@@ -82271,7 +82652,8 @@ async function rankKeyEvents(candidates, options, peakLookup) {
|
|
|
82271
82652
|
...t.markForTrain !== void 0 ? { markForTrain: t.markForTrain } : {},
|
|
82272
82653
|
...t.retrainStatus !== void 0 ? { retrainStatus: t.retrainStatus } : {},
|
|
82273
82654
|
...t.debug !== void 0 ? { debug: t.debug } : {},
|
|
82274
|
-
...t.favourited !== void 0 ? { favourited: t.favourited } : {}
|
|
82655
|
+
...t.favourited !== void 0 ? { favourited: t.favourited } : {},
|
|
82656
|
+
...t.audioLabels !== void 0 && t.audioLabels.length > 0 ? { audioLabels: t.audioLabels } : {}
|
|
82275
82657
|
});
|
|
82276
82658
|
}
|
|
82277
82659
|
scored.sort((a, b) => b.importance - a.importance);
|
|
@@ -82698,16 +83080,19 @@ var MotionOnsetRegistry = class {
|
|
|
82698
83080
|
* - `resolveSearchThumbnailUrl` — the search-hit thumbnail resolver. It points
|
|
82699
83081
|
* a hit at the CLIP embedding row's OWN crop (the crop captured on the
|
|
82700
83082
|
* embedding's peak frame → the hit thumbnail IS the embedded crop), and only
|
|
82701
|
-
* falls back to the representative event's
|
|
82702
|
-
* crop yet, so a hit never renders blank.
|
|
83083
|
+
* falls back to the representative event's owner ladder when the embedding
|
|
83084
|
+
* row has no crop yet, so a hit never renders blank.
|
|
82703
83085
|
*/
|
|
82704
83086
|
function isClipObjectEmbedding(t, registry = BUILTIN_CLIP_MODELS) {
|
|
82705
83087
|
return Array.isArray(t.embedding) && t.embedding.length > 0 && typeof t.embeddingModelId === "string" && registry.resolve(t.embeddingModelId) !== null;
|
|
82706
83088
|
}
|
|
82707
83089
|
function resolveSearchThumbnailUrl(input) {
|
|
82708
83090
|
if (input.baseUrl === null) return void 0;
|
|
82709
|
-
|
|
82710
|
-
return
|
|
83091
|
+
if (input.embeddingMediaId !== void 0) return mediaFileUrl(input.baseUrl, input.embeddingMediaId);
|
|
83092
|
+
return mediaOwnerUrl(input.baseUrl, "object", input.eventId, {
|
|
83093
|
+
deviceId: input.deviceId,
|
|
83094
|
+
recordAt: input.recordAt
|
|
83095
|
+
});
|
|
82711
83096
|
}
|
|
82712
83097
|
//#endregion
|
|
82713
83098
|
//#region src/pipeline-analytics/pipeline/onboard-birth-authority.ts
|
|
@@ -93397,6 +93782,7 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
93397
93782
|
const { trackIds, eventIdsByTable } = questionsFor(refs);
|
|
93398
93783
|
let liveTracks;
|
|
93399
93784
|
const liveEvents = /* @__PURE__ */ new Map();
|
|
93785
|
+
const eventGens = /* @__PURE__ */ new Map();
|
|
93400
93786
|
let keptMedia = /* @__PURE__ */ new Set();
|
|
93401
93787
|
try {
|
|
93402
93788
|
liveTracks = trackIds.length > 0 ? await deps.liveTrackIds(trackIds) : /* @__PURE__ */ new Set();
|
|
@@ -93404,6 +93790,7 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
93404
93790
|
for (const [table, ids] of eventIdsByTable) {
|
|
93405
93791
|
if (ids.length === 0) continue;
|
|
93406
93792
|
liveEvents.set(table, await deps.liveEventIds(table, ids));
|
|
93793
|
+
if (deps.eventGenerations !== void 0 && scope.collection === MEDIA_COLLECTION) eventGens.set(table, await deps.eventGenerations(table, ids));
|
|
93407
93794
|
}
|
|
93408
93795
|
} catch (err) {
|
|
93409
93796
|
deps.logger.warn("orphan audit: liveness probe failed — scope abandoned for this run", { meta: {
|
|
@@ -93432,7 +93819,13 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
93432
93819
|
tally.unresolvable += 1;
|
|
93433
93820
|
continue;
|
|
93434
93821
|
}
|
|
93435
|
-
|
|
93822
|
+
let live = ref.kind === "track" ? liveTracks.has(ref.trackId) || keptMedia.has(row.key) : ref.kind === "event" ? liveEvents.get(ref.table)?.has(ref.eventId) ?? false : false;
|
|
93823
|
+
if (live && ref.kind === "event" && scope.collection === MEDIA_COLLECTION) {
|
|
93824
|
+
const gen = eventGens.get(ref.table)?.get(ref.eventId);
|
|
93825
|
+
const mediaDeviceId = num(row.data, scope.deviceIdField);
|
|
93826
|
+
if (gen !== void 0 && mediaDeviceId !== null && mediaDeviceId !== gen.deviceId) live = false;
|
|
93827
|
+
}
|
|
93828
|
+
if (live) continue;
|
|
93436
93829
|
const ts = num(row.data, scope.timestampField);
|
|
93437
93830
|
if (ts !== null && ts > graceFloor) {
|
|
93438
93831
|
tally.withinGrace += 1;
|
|
@@ -93447,7 +93840,7 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
93447
93840
|
break;
|
|
93448
93841
|
}
|
|
93449
93842
|
try {
|
|
93450
|
-
const outcome = await deps.reclaim(scope.collection, row.key);
|
|
93843
|
+
const outcome = await deps.reclaim(scope.collection, row.key, row);
|
|
93451
93844
|
if (outcome.rows === 0) continue;
|
|
93452
93845
|
tally.reclaimed += 1;
|
|
93453
93846
|
reclaimedInPage += 1;
|
|
@@ -93603,7 +93996,10 @@ var ORPHAN_AUDIT_DEFAULTS = {
|
|
|
93603
93996
|
intervalHours: OrphanAuditIntervalHoursSchema.parse(void 0),
|
|
93604
93997
|
pageSize: OrphanAuditPageSizeSchema.parse(void 0)
|
|
93605
93998
|
};
|
|
93606
|
-
|
|
93999
|
+
//#endregion
|
|
94000
|
+
//#region src/pipeline-analytics/retention/orphan-reclaimers.ts
|
|
94001
|
+
var OBJECT_EVENTS_COLLECTION = "pipeline-analytics:object-events";
|
|
94002
|
+
var RAW_DELETE_COLLECTIONS = new Set([OBJECT_EVENTS_COLLECTION, "pipeline-analytics:plates"]);
|
|
93607
94003
|
/**
|
|
93608
94004
|
* The `deleteRow` port: ONE row by its primary key `id` (what every
|
|
93609
94005
|
* raw-delete collection above declares), answering whether a row went.
|
|
@@ -93612,12 +94008,19 @@ var RAW_DELETE_COLLECTIONS = new Set(["pipeline-analytics:object-events", "pipel
|
|
|
93612
94008
|
* is `z.void()`, and reading it as `!== undefined` answered `false` for every
|
|
93613
94009
|
* row it really deleted — the audit counted every plates/object-events reclaim
|
|
93614
94010
|
* as "found nothing", and the per-scope reclaim ceiling never tripped.
|
|
94011
|
+
*
|
|
94012
|
+
* When `assert.trackId` is set, the delete also requires that column: an
|
|
94013
|
+
* object-events rowid alone is not an identity across the row's life (D694).
|
|
93615
94014
|
*/
|
|
93616
94015
|
function settingsRowDeleter(store) {
|
|
93617
|
-
return async (collection, key) => {
|
|
94016
|
+
return async (collection, key, assert) => {
|
|
94017
|
+
const where = assert !== void 0 ? {
|
|
94018
|
+
id: key,
|
|
94019
|
+
trackId: assert.trackId
|
|
94020
|
+
} : { id: key };
|
|
93618
94021
|
const { deleted } = await store.deleteWhere.mutate({
|
|
93619
94022
|
collection,
|
|
93620
|
-
filter: { where
|
|
94023
|
+
filter: { where }
|
|
93621
94024
|
});
|
|
93622
94025
|
return deleted > 0;
|
|
93623
94026
|
};
|
|
@@ -93632,14 +94035,19 @@ function settingsRowDeleter(store) {
|
|
|
93632
94035
|
* `scripts/check-orphan-reclaimers.ts` checks `index.ts` uses it.
|
|
93633
94036
|
*/
|
|
93634
94037
|
function settingsRowDeleterOf(getStore) {
|
|
93635
|
-
return async (collection, key) => {
|
|
94038
|
+
return async (collection, key, assert) => {
|
|
93636
94039
|
const store = getStore();
|
|
93637
94040
|
if (store === null) throw new Error("orphan audit: settings store not bound");
|
|
93638
|
-
return await settingsRowDeleter(store)(collection, key);
|
|
94041
|
+
return await settingsRowDeleter(store)(collection, key, assert);
|
|
93639
94042
|
};
|
|
93640
94043
|
}
|
|
94044
|
+
/** Pull the trackId the raw delete must re-assert, or `null` when absent. */
|
|
94045
|
+
function trackIdAssertOf(data) {
|
|
94046
|
+
const trackId = data["trackId"];
|
|
94047
|
+
return typeof trackId === "string" && trackId.length > 0 ? { trackId } : null;
|
|
94048
|
+
}
|
|
93641
94049
|
function buildOrphanReclaimer(ports) {
|
|
93642
|
-
return async (collection, key) => {
|
|
94050
|
+
return async (collection, key, data = {}) => {
|
|
93643
94051
|
if (collection === MEDIA_COLLECTION) {
|
|
93644
94052
|
const mediaId = Number(key);
|
|
93645
94053
|
if (!Number.isInteger(mediaId) || mediaId <= 0) {
|
|
@@ -93665,10 +94073,23 @@ function buildOrphanReclaimer(ports) {
|
|
|
93665
94073
|
rows: await ports.deleteTrackAlias(key) ? 1 : 0,
|
|
93666
94074
|
bytes: null
|
|
93667
94075
|
};
|
|
93668
|
-
if (RAW_DELETE_COLLECTIONS.has(collection))
|
|
93669
|
-
|
|
93670
|
-
|
|
93671
|
-
|
|
94076
|
+
if (RAW_DELETE_COLLECTIONS.has(collection)) {
|
|
94077
|
+
const assert = trackIdAssertOf(data);
|
|
94078
|
+
if (collection === OBJECT_EVENTS_COLLECTION && assert === null) {
|
|
94079
|
+
ports.logger.warn("orphan audit: object-events reclaim without trackId — row kept (id-only delete refused)", { meta: {
|
|
94080
|
+
collection,
|
|
94081
|
+
key
|
|
94082
|
+
} });
|
|
94083
|
+
return {
|
|
94084
|
+
rows: 0,
|
|
94085
|
+
bytes: null
|
|
94086
|
+
};
|
|
94087
|
+
}
|
|
94088
|
+
return {
|
|
94089
|
+
rows: await ports.deleteRow(collection, key, assert === null ? void 0 : assert) ? 1 : 0,
|
|
94090
|
+
bytes: null
|
|
94091
|
+
};
|
|
94092
|
+
}
|
|
93672
94093
|
throw new Error(`orphan audit: '${collection}' is not declared in RECLAIMED_COLLECTIONS — a collection walked by the audit must say how its rows are deleted, because the raw path leaves behind anything its store owns outside the row`);
|
|
93673
94094
|
};
|
|
93674
94095
|
}
|
|
@@ -100831,7 +101252,10 @@ function ofGeneration(files, scope) {
|
|
|
100831
101252
|
function makeNcOwnerMediaReader(deps) {
|
|
100832
101253
|
return async (ownerKind, ownerId, eventScope) => {
|
|
100833
101254
|
if (ownerKind === "track") return deps.media.listByOwner("track", await deps.resolveTrackOwnerId(ownerId));
|
|
100834
|
-
if (eventScope === void 0)
|
|
101255
|
+
if (eventScope === void 0) {
|
|
101256
|
+
deps.logger.error("object media read refused — NcEventMediaScope required", { meta: { ownerId } });
|
|
101257
|
+
return [];
|
|
101258
|
+
}
|
|
100835
101259
|
const onCamera = await deps.media.listByOwner("object", ownerId, eventScope.deviceId);
|
|
100836
101260
|
const kept = ofGeneration(onCamera, eventScope);
|
|
100837
101261
|
if (kept.length < onCamera.length) deps.logger.warn("event media of another record on a reused event id — not used", {
|
|
@@ -101068,8 +101492,14 @@ async function buildTrainingExportPlan(readers, input) {
|
|
|
101068
101492
|
const eventsByTrack = /* @__PURE__ */ new Map();
|
|
101069
101493
|
for (const link of eventLinks) {
|
|
101070
101494
|
const list = eventsByTrack.get(link.trackId);
|
|
101071
|
-
if (list) list.push(
|
|
101072
|
-
|
|
101495
|
+
if (list) list.push({
|
|
101496
|
+
eventId: link.eventId,
|
|
101497
|
+
recordAt: link.recordAt
|
|
101498
|
+
});
|
|
101499
|
+
else eventsByTrack.set(link.trackId, [{
|
|
101500
|
+
eventId: link.eventId,
|
|
101501
|
+
recordAt: link.recordAt
|
|
101502
|
+
}]);
|
|
101073
101503
|
}
|
|
101074
101504
|
for (const track of take) {
|
|
101075
101505
|
const files = [];
|
|
@@ -101089,15 +101519,17 @@ async function buildTrainingExportPlan(readers, input) {
|
|
|
101089
101519
|
ownerId: `${PLATE_OWNER_PREFIX}${track.trackId}`,
|
|
101090
101520
|
scope: "plate"
|
|
101091
101521
|
},
|
|
101092
|
-
...(eventsByTrack.get(track.trackId) ?? []).map((
|
|
101522
|
+
...(eventsByTrack.get(track.trackId) ?? []).map((ev) => ({
|
|
101093
101523
|
ownerKind: "object",
|
|
101094
|
-
ownerId: eventId,
|
|
101095
|
-
scope: `events/${eventId}
|
|
101524
|
+
ownerId: ev.eventId,
|
|
101525
|
+
scope: `events/${ev.eventId}`,
|
|
101526
|
+
recordAt: ev.recordAt
|
|
101096
101527
|
}))
|
|
101097
101528
|
];
|
|
101098
101529
|
for (const owner of owners) {
|
|
101099
|
-
const infos = await readers.listMediaInfo(owner.ownerKind, owner.ownerId);
|
|
101100
|
-
|
|
101530
|
+
const infos = await readers.listMediaInfo(owner.ownerKind, owner.ownerId, owner.ownerKind === "object" ? track.deviceId : void 0);
|
|
101531
|
+
const kept = owner.recordAt === void 0 ? infos : infos.filter((info) => info.timestamp === owner.recordAt);
|
|
101532
|
+
for (const info of kept) {
|
|
101101
101533
|
const archivePath = archivePathFor({
|
|
101102
101534
|
deviceId: track.deviceId,
|
|
101103
101535
|
trackId: track.trackId,
|
|
@@ -101916,6 +102348,10 @@ var NC_ARTIFACT_PRUNE_INTERVAL_MS = 36e5;
|
|
|
101916
102348
|
* (every camera switched off): the bound is 24 h, not "24 h after the next
|
|
101917
102349
|
* close" (D681). */
|
|
101918
102350
|
var TAPE_SWEEP_INTERVAL_MS = 36e5;
|
|
102351
|
+
/** How often open tape segments push their pending gzip output to disk
|
|
102352
|
+
* without rotating (D681 follow-up): a silent camera otherwise keeps its
|
|
102353
|
+
* tail only in the zlib buffer until the next frame or the 10-minute cut. */
|
|
102354
|
+
var TAPE_FLUSH_INTERVAL_MS = 6e4;
|
|
101919
102355
|
/** What a publish answers on a node whose artifact plane never came up: no
|
|
101920
102356
|
* link, no index row — and, exactly as before, bytes-only delivery. */
|
|
101921
102357
|
var NO_PUBLICATION = {
|
|
@@ -102449,6 +102885,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
102449
102885
|
ncArtifactReadDataPlane = null;
|
|
102450
102886
|
ncArtifactPruneTimer = null;
|
|
102451
102887
|
tapeSweepTimer = null;
|
|
102888
|
+
tapeFlushTimer = null;
|
|
102452
102889
|
/** Why `startTrackingTapes` threw on a node that was allowed to record —
|
|
102453
102890
|
* reported `failing` per switched-on camera, never as `recording`. */
|
|
102454
102891
|
tapeStartFailure = null;
|
|
@@ -104253,6 +104690,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
104253
104690
|
StationaryObjectRegistry.declare(api.settingsStore),
|
|
104254
104691
|
RetrainFrameStore.declare(api.settingsStore),
|
|
104255
104692
|
RetrainAnnotationStore.declare(api.settingsStore),
|
|
104693
|
+
RetrainCollectionStore.declare(api.settingsStore),
|
|
104256
104694
|
OpsLogStore.declare(api.settingsStore),
|
|
104257
104695
|
DebugNoteArchiveStore.declare(api.settingsStore),
|
|
104258
104696
|
TrackingTapeIndexStore.declare(api.settingsStore),
|
|
@@ -105608,15 +106046,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
105608
106046
|
if (!trackStore || !mediaStore) throw new Error("training export: analytics stores unavailable");
|
|
105609
106047
|
return buildTrainingExportPlan({
|
|
105610
106048
|
listMarkedTracks: (deviceId, limit) => trackStore.listStaging(deviceId, limit),
|
|
105611
|
-
listMediaInfo: async (ownerKind, ownerId) => {
|
|
106049
|
+
listMediaInfo: async (ownerKind, ownerId, deviceId) => {
|
|
105612
106050
|
if (!isMediaOwnerKind(ownerKind)) return [];
|
|
105613
|
-
return mediaStore.listInfoByOwner(ownerKind, ownerId);
|
|
106051
|
+
return mediaStore.listInfoByOwner(ownerKind, ownerId, deviceId);
|
|
105614
106052
|
},
|
|
105615
106053
|
listEventIdsForTracks: async (trackIds) => {
|
|
105616
106054
|
if (!eventStore || trackIds.length === 0) return [];
|
|
105617
106055
|
return (await eventStore.queryObjectByTrackIds(trackIds)).flatMap((e) => e.trackId === void 0 ? [] : [{
|
|
105618
106056
|
eventId: String(e.id),
|
|
105619
|
-
trackId: e.trackId
|
|
106057
|
+
trackId: e.trackId,
|
|
106058
|
+
recordAt: e.timestamp
|
|
105620
106059
|
}]);
|
|
105621
106060
|
},
|
|
105622
106061
|
now: () => Date.now()
|
|
@@ -106700,6 +107139,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
106700
107139
|
clearInterval(this.tapeSweepTimer);
|
|
106701
107140
|
this.tapeSweepTimer = null;
|
|
106702
107141
|
}
|
|
107142
|
+
if (this.tapeFlushTimer !== null) {
|
|
107143
|
+
clearInterval(this.tapeFlushTimer);
|
|
107144
|
+
this.tapeFlushTimer = null;
|
|
107145
|
+
}
|
|
106703
107146
|
if (this.ncArtifactPruneTimer !== null) {
|
|
106704
107147
|
clearInterval(this.ncArtifactPruneTimer);
|
|
106705
107148
|
this.ncArtifactPruneTimer = null;
|
|
@@ -111817,26 +112260,30 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
111817
112260
|
},
|
|
111818
112261
|
liveTrackIds: (ids) => this.liveTrackIdsThroughAliases(ids, trackStore),
|
|
111819
112262
|
liveEventIds: (table, ids) => eventStore.existingEventIdsIn(table, ids),
|
|
112263
|
+
eventGenerations: (table, ids) => eventStore.eventGenerationsIn(table, ids),
|
|
111820
112264
|
keptMediaKeys: async (keys) => this.keptStationaryMediaKeys(keys),
|
|
111821
|
-
reclaim:
|
|
111822
|
-
|
|
111823
|
-
|
|
111824
|
-
|
|
111825
|
-
|
|
111826
|
-
|
|
111827
|
-
|
|
111828
|
-
|
|
111829
|
-
|
|
111830
|
-
|
|
111831
|
-
|
|
111832
|
-
|
|
111833
|
-
|
|
111834
|
-
|
|
111835
|
-
|
|
111836
|
-
|
|
111837
|
-
|
|
111838
|
-
|
|
111839
|
-
|
|
112265
|
+
reclaim: (() => {
|
|
112266
|
+
const reclaimOne = buildOrphanReclaimer({
|
|
112267
|
+
deleteFace: async (faceId) => {
|
|
112268
|
+
await this.faceStore?.delete(faceId);
|
|
112269
|
+
},
|
|
112270
|
+
deleteMediaConfirmed: async (mediaId) => {
|
|
112271
|
+
const count = await mediaStore.deleteConfirmed(mediaId);
|
|
112272
|
+
return {
|
|
112273
|
+
rows: count.rows,
|
|
112274
|
+
bytes: count.bytes
|
|
112275
|
+
};
|
|
112276
|
+
},
|
|
112277
|
+
deleteTrackAlias: async (aliasId) => {
|
|
112278
|
+
const aliasStore = this.trackAliasStore;
|
|
112279
|
+
if (aliasStore === null) throw new Error("track alias store not bound");
|
|
112280
|
+
return await aliasStore.deleteAlias(aliasId);
|
|
112281
|
+
},
|
|
112282
|
+
deleteRow: settingsRowDeleterOf(() => this.settingsStore),
|
|
112283
|
+
logger: this.ctx.logger
|
|
112284
|
+
});
|
|
112285
|
+
return (collection, key, row) => reclaimOne(collection, key, row.data);
|
|
112286
|
+
})(),
|
|
111840
112287
|
now: () => Date.now(),
|
|
111841
112288
|
logger: this.ctx.logger,
|
|
111842
112289
|
breathe: () => new Promise((resolve) => setImmediate(resolve))
|
|
@@ -113469,10 +113916,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
113469
113916
|
});
|
|
113470
113917
|
const scored = [];
|
|
113471
113918
|
for (const { event, score } of bestEventByTrackId.values()) {
|
|
113919
|
+
const embeddingMediaId = event.trackId !== void 0 ? embeddingMediaIdByTrackId.get(event.trackId) : void 0;
|
|
113472
113920
|
const mediaUrl = resolveSearchThumbnailUrl({
|
|
113473
113921
|
baseUrl: this.eventMediaBaseUrl,
|
|
113474
113922
|
eventId: String(event.id),
|
|
113475
|
-
|
|
113923
|
+
deviceId: event.deviceId,
|
|
113924
|
+
recordAt: event.timestamp,
|
|
113925
|
+
...embeddingMediaId !== void 0 ? { embeddingMediaId } : {}
|
|
113476
113926
|
});
|
|
113477
113927
|
const keyFrameMediaId = event.trackId !== void 0 ? keyFrameMediaIdByTrackId.get(event.trackId) : void 0;
|
|
113478
113928
|
scored.push({
|
|
@@ -113859,7 +114309,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
113859
114309
|
logger
|
|
113860
114310
|
});
|
|
113861
114311
|
const sweep = (deviceId) => {
|
|
113862
|
-
shelf.sweep().
|
|
114312
|
+
shelf.sweep().then((report) => {
|
|
114313
|
+
logTapeSweepReport(report, logger);
|
|
114314
|
+
}).catch((err) => {
|
|
113863
114315
|
logger.warn("tracking tape sweep failed", {
|
|
113864
114316
|
...deviceId !== null ? { tags: { deviceId } } : {},
|
|
113865
114317
|
meta: { error: errMsg(err) }
|
|
@@ -113891,6 +114343,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
113891
114343
|
sweep(null);
|
|
113892
114344
|
this.tapeSweepTimer = setInterval(() => sweep(null), TAPE_SWEEP_INTERVAL_MS);
|
|
113893
114345
|
this.tapeSweepTimer.unref?.();
|
|
114346
|
+
this.tapeFlushTimer = setInterval(() => {
|
|
114347
|
+
this.tapeRecorder?.flushOpenSegments();
|
|
114348
|
+
}, TAPE_FLUSH_INTERVAL_MS);
|
|
114349
|
+
this.tapeFlushTimer.unref?.();
|
|
113894
114350
|
this.ctx.logger.info("tracking tapes ready", { meta: { seededProcessors: this.processorInputs.size } });
|
|
113895
114351
|
} catch (err) {
|
|
113896
114352
|
this.tapeStartFailure = errMsg(err);
|
|
@@ -114083,7 +114539,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
114083
114539
|
segments.set(deviceId, await this.tapeListingOf(shelf, deviceId));
|
|
114084
114540
|
}
|
|
114085
114541
|
const recorder = this.tapeRecorder;
|
|
114086
|
-
|
|
114542
|
+
const cameras = describeTapeStatus({
|
|
114087
114543
|
deviceIds,
|
|
114088
114544
|
switchOn,
|
|
114089
114545
|
switchUnreadable,
|
|
@@ -114092,7 +114548,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
114092
114548
|
startFailure: this.tapeStartFailure,
|
|
114093
114549
|
segments,
|
|
114094
114550
|
now: Date.now()
|
|
114095
|
-
})
|
|
114551
|
+
});
|
|
114552
|
+
return { cameras: input.deviceIds === void 0 ? defaultTapeStatusCameras(cameras) : cameras };
|
|
114096
114553
|
},
|
|
114097
114554
|
"debug.tape.promote": async (input) => {
|
|
114098
114555
|
const deps = this.tapePromotionDeps();
|
|
@@ -114541,7 +114998,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
114541
114998
|
* owner table, so the motion/object coin-flip is gone from here too.
|
|
114542
114999
|
*/
|
|
114543
115000
|
async readMediaByAddress(address, variant, preferKind) {
|
|
114544
|
-
const base = address.kind === "row" ? await this.readMediaById(address.mediaId) : await this.readOwnerThumbnail(address.ownerType, address.ownerId, preferKind);
|
|
115001
|
+
const base = address.kind === "row" ? await this.readMediaById(address.mediaId) : await this.readOwnerThumbnail(address.ownerType, address.ownerId, preferKind, address.generation);
|
|
114545
115002
|
if (base === null || variant === void 0) return base;
|
|
114546
115003
|
return this.applyThumbVariant(base, variant);
|
|
114547
115004
|
}
|
|
@@ -114599,9 +115056,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
114599
115056
|
* ~144 MB and allocated ~190 MB of base64 to deliver ~1.2 MB, all of it on
|
|
114600
115057
|
* the single-threaded post-analysis runner.
|
|
114601
115058
|
*/
|
|
114602
|
-
async readOwnerThumbnail(ownerType, rawOwnerId, preferKind) {
|
|
115059
|
+
async readOwnerThumbnail(ownerType, rawOwnerId, preferKind, addressGeneration) {
|
|
114603
115060
|
const ownerId = ownerType === "track" ? await resolveMediaOwnerId(ownerType, rawOwnerId, (id) => this.trackAliasStore?.resolve(id) ?? Promise.resolve(null), (failure) => this.aliasResolveFailureLogger?.(failure), "readOwnerThumbnail") : rawOwnerId;
|
|
114604
|
-
const listInfo = (ownerKind, id) => this.mediaStore?.listInfoByOwner(ownerKind, id) ?? Promise.resolve([]);
|
|
115061
|
+
const listInfo = (ownerKind, id, deviceId) => this.mediaStore?.listInfoByOwner(ownerKind, id, deviceId) ?? Promise.resolve([]);
|
|
114605
115062
|
const getTrackIdForEvent = (eventId) => isEventOwnerType(ownerType) ? this.eventStore?.getTrackIdForEvent(eventId, ownerType) ?? Promise.resolve(null) : Promise.resolve(null);
|
|
114606
115063
|
const readBlob = async (mediaId) => {
|
|
114607
115064
|
const file = await (this.mediaStore?.getById(mediaId) ?? Promise.resolve(null));
|
|
@@ -114612,7 +115069,30 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
114612
115069
|
deviceId: file.deviceId
|
|
114613
115070
|
};
|
|
114614
115071
|
};
|
|
114615
|
-
|
|
115072
|
+
let ownFiles = await listInfo(ownerType, ownerId);
|
|
115073
|
+
if (isEventOwnerType(ownerType)) {
|
|
115074
|
+
const gen = addressGeneration !== void 0 ? {
|
|
115075
|
+
deviceId: addressGeneration.deviceId,
|
|
115076
|
+
timestamp: addressGeneration.recordAt
|
|
115077
|
+
} : this.eventStore !== null ? await this.eventStore.eventGenerationOf(ownerType, ownerId) : null;
|
|
115078
|
+
if (gen !== null) {
|
|
115079
|
+
const onCamera = await listInfo(ownerType, ownerId, gen.deviceId);
|
|
115080
|
+
const kept = onCamera.filter((f) => f.timestamp === gen.timestamp);
|
|
115081
|
+
if (kept.length < onCamera.length) this.ctx.logger.warn("event media of another record on a reused event id — not used", {
|
|
115082
|
+
tags: { deviceId: gen.deviceId },
|
|
115083
|
+
meta: {
|
|
115084
|
+
ownerId,
|
|
115085
|
+
ownerType,
|
|
115086
|
+
recordAt: gen.timestamp,
|
|
115087
|
+
refused: onCamera.length - kept.length,
|
|
115088
|
+
kept: kept.length,
|
|
115089
|
+
via: "readOwnerThumbnail",
|
|
115090
|
+
scopedBy: addressGeneration !== void 0 ? "url" : "live-row"
|
|
115091
|
+
}
|
|
115092
|
+
});
|
|
115093
|
+
ownFiles = kept;
|
|
115094
|
+
}
|
|
115095
|
+
}
|
|
114616
115096
|
if (preferKind !== void 0 && preferKind.length > 0) {
|
|
114617
115097
|
const layers = await collectCropForcedMedia({
|
|
114618
115098
|
listByOwner: listInfo,
|