@camstack/addon-post-analysis 1.2.306 → 1.2.308
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-C67UM_q_.js → clip-model-registry-BqrTKyUe.js} +346 -267
- package/dist/{clip-model-registry-C9Aw4qkh.mjs → clip-model-registry-FGOIz3PZ.mjs} +346 -267
- 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-QE0Juzwb.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-61DKRi3y.mjs} +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C-9GoNIB.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-DzNrV8y8.mjs → hostInit-BeNsqz7j.mjs} +1 -1
- package/dist/pipeline-analytics/index.js +858 -265
- package/dist/pipeline-analytics/index.mjs +858 -265
- 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-MwQBD526.mjs +0 -26
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_clip_model_registry = require("../clip-model-registry-
|
|
5
|
+
const require_clip_model_registry = require("../clip-model-registry-BqrTKyUe.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_clip_model_registry.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -20071,6 +20071,114 @@ function ncGroupSubjectText(texts, input) {
|
|
|
20071
20071
|
return texts.join([...names, ...others]);
|
|
20072
20072
|
}
|
|
20073
20073
|
//#endregion
|
|
20074
|
+
//#region src/notification-center/outbox-key.ts
|
|
20075
|
+
/**
|
|
20076
|
+
* The record kinds whose `recordId` is a per-table SQLite rowid (D474), and so
|
|
20077
|
+
* may be reused after a delete. `device-event` is NOT one: sensor events are
|
|
20078
|
+
* TEXT-keyed with a producer-minted id (`sensor-event-store.ts`).
|
|
20079
|
+
*/
|
|
20080
|
+
var ROWID_RECORD_KINDS = new Set([
|
|
20081
|
+
"object-event",
|
|
20082
|
+
"package-event",
|
|
20083
|
+
"audio-event"
|
|
20084
|
+
]);
|
|
20085
|
+
/** Is this kind's record id a reusable rowid? See {@link ROWID_RECORD_KINDS}. */
|
|
20086
|
+
function isRowidRecordKind(kind) {
|
|
20087
|
+
return ROWID_RECORD_KINDS.has(kind);
|
|
20088
|
+
}
|
|
20089
|
+
/** The record identity an outbox key input speaks for. */
|
|
20090
|
+
function outboxRecordIdentity(input) {
|
|
20091
|
+
return {
|
|
20092
|
+
recordId: input.recordId,
|
|
20093
|
+
deviceId: input.deviceId,
|
|
20094
|
+
recordAt: input.payload.subject.timestamp
|
|
20095
|
+
};
|
|
20096
|
+
}
|
|
20097
|
+
/**
|
|
20098
|
+
* The record GENERATION ref — `<recordId>@<deviceId>:<recordAt>`. Exported for
|
|
20099
|
+
* the specs and for a caller that must name a rowid record outside the outbox
|
|
20100
|
+
* (none today); a key is always built through {@link outboxEntryId}.
|
|
20101
|
+
*/
|
|
20102
|
+
function recordGenerationRef(record) {
|
|
20103
|
+
return `${record.recordId}@${String(record.deviceId)}:${String(record.recordAt)}`;
|
|
20104
|
+
}
|
|
20105
|
+
/**
|
|
20106
|
+
* The middle segment of the dedup id:
|
|
20107
|
+
*
|
|
20108
|
+
* 1. an explicit `dedupRef` (upgrade / burst revision);
|
|
20109
|
+
* 2. the track id — `maxPerTrack: 1` (a track id is random, never reused);
|
|
20110
|
+
* 3. for a rowid record, its generation ref;
|
|
20111
|
+
* 4. the record id as is (synthetic ids already carry device + instant).
|
|
20112
|
+
*/
|
|
20113
|
+
function outboxDedupRef(input) {
|
|
20114
|
+
if (input.dedupRef !== void 0) return input.dedupRef;
|
|
20115
|
+
if (input.trackId !== void 0) return input.trackId;
|
|
20116
|
+
if (isRowidRecordKind(input.recordKind)) return recordGenerationRef(outboxRecordIdentity(input));
|
|
20117
|
+
return input.recordId;
|
|
20118
|
+
}
|
|
20119
|
+
/** The outbox row id — the durable per-track / per-record fire guarantee. */
|
|
20120
|
+
function outboxEntryId(input) {
|
|
20121
|
+
return `${input.ruleId}:${outboxDedupRef(input)}:${input.targetId}`;
|
|
20122
|
+
}
|
|
20123
|
+
/**
|
|
20124
|
+
* The id this input had BEFORE D694 — but only when it differs from today's,
|
|
20125
|
+
* i.e. for a rowid record keyed on its record id. `null` otherwise: there is no
|
|
20126
|
+
* older spelling to look for.
|
|
20127
|
+
*/
|
|
20128
|
+
function legacyOutboxEntryId(input) {
|
|
20129
|
+
if (input.dedupRef !== void 0 || input.trackId !== void 0) return null;
|
|
20130
|
+
if (!isRowidRecordKind(input.recordKind)) return null;
|
|
20131
|
+
return `${input.ruleId}:${input.recordId}:${input.targetId}`;
|
|
20132
|
+
}
|
|
20133
|
+
/**
|
|
20134
|
+
* A STORED row's id as it would be built today, when the row was written under
|
|
20135
|
+
* the pre-D694 spelling; `null` when the stored id is already current (or was
|
|
20136
|
+
* built from an explicit dedup ref, which a row does not record and which never
|
|
20137
|
+
* involved the record id).
|
|
20138
|
+
*/
|
|
20139
|
+
function translatedLegacyEntryId(storedId, row) {
|
|
20140
|
+
const legacy = legacyOutboxEntryId(row);
|
|
20141
|
+
if (legacy === null || legacy !== storedId) return null;
|
|
20142
|
+
return outboxEntryId(row);
|
|
20143
|
+
}
|
|
20144
|
+
/**
|
|
20145
|
+
* Does a STORED row's payload really carry its record timestamp?
|
|
20146
|
+
*
|
|
20147
|
+
* The type says it always does, but a stored payload is checked only as an
|
|
20148
|
+
* object on the way back from the store (`rowToEntry`), so a malformed or very
|
|
20149
|
+
* old row can lack `subject`. Every read of {@link outboxRecordIdentity} on a
|
|
20150
|
+
* STORED row is preceded by this check. Takes the payload as `unknown` on
|
|
20151
|
+
* purpose: the static type cannot answer the question.
|
|
20152
|
+
*/
|
|
20153
|
+
function hasRecordTimestamp(payload) {
|
|
20154
|
+
if (typeof payload !== "object" || payload === null || !("subject" in payload)) return false;
|
|
20155
|
+
const subject = payload.subject;
|
|
20156
|
+
return typeof subject === "object" && subject !== null && "timestamp" in subject && typeof subject.timestamp === "number";
|
|
20157
|
+
}
|
|
20158
|
+
/** Do two rows speak for the same RECORD? Id, camera and record timestamp. */
|
|
20159
|
+
function isSameOutboxRecord(a, b) {
|
|
20160
|
+
return a.recordId === b.recordId && a.deviceId === b.deviceId && a.recordAt === b.recordAt;
|
|
20161
|
+
}
|
|
20162
|
+
/**
|
|
20163
|
+
* The scope of the event a payload's `eventId` names.
|
|
20164
|
+
*
|
|
20165
|
+
* Usually the row's own subject. A COMBINED alarm row is the exception: its
|
|
20166
|
+
* subject is the alarm transition (its own instant), while its `eventId` is the
|
|
20167
|
+
* triggering rule's event — so the generation is the alarm SOURCE's camera and
|
|
20168
|
+
* timestamp (`NcRuleFireRef`), never the transition's.
|
|
20169
|
+
*/
|
|
20170
|
+
function eventMediaScopeOf(subject) {
|
|
20171
|
+
const source = subject.systemEvent?.alarmSource;
|
|
20172
|
+
if (source !== void 0) return {
|
|
20173
|
+
deviceId: source.deviceId,
|
|
20174
|
+
recordAt: source.timestamp
|
|
20175
|
+
};
|
|
20176
|
+
return {
|
|
20177
|
+
deviceId: subject.deviceId,
|
|
20178
|
+
recordAt: subject.timestamp
|
|
20179
|
+
};
|
|
20180
|
+
}
|
|
20181
|
+
//#endregion
|
|
20074
20182
|
//#region src/notification-center/footage-source.ts
|
|
20075
20183
|
/**
|
|
20076
20184
|
* Decide where this window must be cut from. Pure and total.
|
|
@@ -22036,7 +22144,7 @@ var NcDispatcher = class {
|
|
|
22036
22144
|
});
|
|
22037
22145
|
if (policy === "keyFrame" || frame === "full" || frame === "boxed") owners.reverse();
|
|
22038
22146
|
for (const owner of owners) try {
|
|
22039
|
-
const files = await this.deps.getMediaForOwner(owner.kind, owner.id);
|
|
22147
|
+
const files = owner.kind === "object" ? await this.deps.getMediaForOwner(owner.kind, owner.id, eventMediaScopeOf(subject)) : await this.deps.getMediaForOwner(owner.kind, owner.id);
|
|
22040
22148
|
if (files.length === 0) continue;
|
|
22041
22149
|
const preference = frame !== void 0 ? framePreference(frame, owner.kind) : policy === "best-matching" ? bestMatchingKindPreference(signal, owner.kind) : attachmentKindPreference(policy, owner.kind);
|
|
22042
22150
|
const ordered = [];
|
|
@@ -24321,11 +24429,6 @@ var DEFAULT_MAX_ATTEMPTS = 8;
|
|
|
24321
24429
|
var DEFAULT_DRAIN_BATCH = 20;
|
|
24322
24430
|
/** Dedup ids remembered at boot — bounded to recent rows (dedup horizon). */
|
|
24323
24431
|
var DEDUP_HYDRATE_WINDOW_MS = 168 * 36e5;
|
|
24324
|
-
/** Stable dedup id — the durable per-track/per-record fire guarantee. */
|
|
24325
|
-
function outboxEntryId(input) {
|
|
24326
|
-
const dedupRef = input.dedupRef ?? input.trackId ?? input.recordId;
|
|
24327
|
-
return `${input.ruleId}:${dedupRef}:${input.targetId}`;
|
|
24328
|
-
}
|
|
24329
24432
|
var NcOutbox = class {
|
|
24330
24433
|
pending = /* @__PURE__ */ new Map();
|
|
24331
24434
|
/** Every id ever enqueued within the dedup horizon (incl. sent/dead). */
|
|
@@ -24402,20 +24505,27 @@ var NcOutbox = class {
|
|
|
24402
24505
|
}
|
|
24403
24506
|
});
|
|
24404
24507
|
let superseded = 0;
|
|
24508
|
+
let translated = 0;
|
|
24405
24509
|
for (const row of rows) {
|
|
24406
|
-
|
|
24510
|
+
const entry = rowToEntry$1(row.id, row.data);
|
|
24511
|
+
const current = entry === null ? null : this.translateLegacy(row.id, entry);
|
|
24512
|
+
if (this.knownIds.has(row.id) || current !== null && this.knownIds.has(current)) {
|
|
24407
24513
|
superseded += 1;
|
|
24408
24514
|
continue;
|
|
24409
24515
|
}
|
|
24410
24516
|
this.knownIds.add(row.id);
|
|
24411
|
-
|
|
24517
|
+
if (current !== null) {
|
|
24518
|
+
this.knownIds.add(current);
|
|
24519
|
+
translated += 1;
|
|
24520
|
+
}
|
|
24412
24521
|
if (entry !== null && entry.status === "pending") this.pending.set(entry.id, entry);
|
|
24413
24522
|
}
|
|
24414
24523
|
this.horizonLoaded = true;
|
|
24415
24524
|
this.logger.info("notification outbox loaded", { meta: {
|
|
24416
24525
|
pending: this.pending.size,
|
|
24417
24526
|
known: this.knownIds.size,
|
|
24418
|
-
superseded
|
|
24527
|
+
superseded,
|
|
24528
|
+
translated
|
|
24419
24529
|
} });
|
|
24420
24530
|
} catch (err) {
|
|
24421
24531
|
this.logger.warn("notification outbox load failed", { meta: { error: String(err) } });
|
|
@@ -24439,7 +24549,7 @@ var NcOutbox = class {
|
|
|
24439
24549
|
for (const input of inputs) {
|
|
24440
24550
|
const id = outboxEntryId(input);
|
|
24441
24551
|
if (this.knownIds.has(id)) continue;
|
|
24442
|
-
if (!this.horizonLoaded && await this.
|
|
24552
|
+
if (!this.horizonLoaded && await this.alreadyStored(id, input)) {
|
|
24443
24553
|
this.knownIds.add(id);
|
|
24444
24554
|
this.logger.info("outbox enqueue deduped against the store — the horizon is still cold", {
|
|
24445
24555
|
tags: { deviceId: input.deviceId },
|
|
@@ -24656,7 +24766,7 @@ var NcOutbox = class {
|
|
|
24656
24766
|
*/
|
|
24657
24767
|
async supersede(input) {
|
|
24658
24768
|
const wanted = new Set(input.targetIds);
|
|
24659
|
-
const victims = [...this.pending.values()].filter((e) => e.ruleId === input.ruleId && e.
|
|
24769
|
+
const victims = [...this.pending.values()].filter((e) => e.ruleId === input.ruleId && wanted.has(e.targetId) && hasRecordTimestamp(e.payload) && isSameOutboxRecord(outboxRecordIdentity(e), input));
|
|
24660
24770
|
let dropped = 0;
|
|
24661
24771
|
for (const entry of victims) try {
|
|
24662
24772
|
await this.store.delete.mutate({
|
|
@@ -24772,6 +24882,81 @@ var NcOutbox = class {
|
|
|
24772
24882
|
await this.mutate(retry);
|
|
24773
24883
|
}
|
|
24774
24884
|
/**
|
|
24885
|
+
* Cold-horizon dedup: is this intent already a row in the store?
|
|
24886
|
+
*
|
|
24887
|
+
* Today's id first. Then, for a rowid record, the pre-D694 spelling — which
|
|
24888
|
+
* is accepted ONLY when the row found speaks for the SAME record (camera and
|
|
24889
|
+
* record timestamp too). A legacy row of a different record that reused the
|
|
24890
|
+
* rowid is exactly the collision D694 removes, and must not swallow this one.
|
|
24891
|
+
*/
|
|
24892
|
+
async alreadyStored(id, input) {
|
|
24893
|
+
if (await this.rowExists(id)) return true;
|
|
24894
|
+
const legacy = legacyOutboxEntryId(input);
|
|
24895
|
+
if (legacy === null) return false;
|
|
24896
|
+
const row = await this.readRow(legacy);
|
|
24897
|
+
if (row === null) return false;
|
|
24898
|
+
if (!hasRecordTimestamp(row.payload)) {
|
|
24899
|
+
this.logger.warn("outbox: a pre-D694 row carries no record timestamp — not used for dedup", {
|
|
24900
|
+
tags: { deviceId: row.deviceId },
|
|
24901
|
+
meta: {
|
|
24902
|
+
id,
|
|
24903
|
+
legacyId: legacy,
|
|
24904
|
+
ruleId: input.ruleId
|
|
24905
|
+
}
|
|
24906
|
+
});
|
|
24907
|
+
return false;
|
|
24908
|
+
}
|
|
24909
|
+
if (isSameOutboxRecord(outboxRecordIdentity(row), outboxRecordIdentity(input))) return true;
|
|
24910
|
+
this.logger.info("outbox: a pre-D694 row shares this rowid but is a different record — not a duplicate", {
|
|
24911
|
+
tags: { deviceId: input.deviceId },
|
|
24912
|
+
meta: {
|
|
24913
|
+
id,
|
|
24914
|
+
legacyId: legacy,
|
|
24915
|
+
legacyDeviceId: row.deviceId,
|
|
24916
|
+
ruleId: input.ruleId
|
|
24917
|
+
}
|
|
24918
|
+
});
|
|
24919
|
+
return false;
|
|
24920
|
+
}
|
|
24921
|
+
/**
|
|
24922
|
+
* The id a stored pre-D694 row would have today, or `null`.
|
|
24923
|
+
*
|
|
24924
|
+
* Guarded per row: a stored payload is validated only as an object
|
|
24925
|
+
* (`rowToEntry`), and a row with no `subject.timestamp` would otherwise throw
|
|
24926
|
+
* inside {@link load} and leave the WHOLE dedup horizon cold. Such a row is
|
|
24927
|
+
* still loaded under its stored id; it just gets no translated twin.
|
|
24928
|
+
*/
|
|
24929
|
+
translateLegacy(storedId, entry) {
|
|
24930
|
+
if (legacyOutboxEntryId(entry) !== storedId) return null;
|
|
24931
|
+
if (!hasRecordTimestamp(entry.payload)) {
|
|
24932
|
+
this.logger.warn("outbox: a pre-D694 row carries no record timestamp — loaded without translation", {
|
|
24933
|
+
tags: { deviceId: entry.deviceId },
|
|
24934
|
+
meta: {
|
|
24935
|
+
id: storedId,
|
|
24936
|
+
ruleId: entry.ruleId
|
|
24937
|
+
}
|
|
24938
|
+
});
|
|
24939
|
+
return null;
|
|
24940
|
+
}
|
|
24941
|
+
return translatedLegacyEntryId(storedId, entry);
|
|
24942
|
+
}
|
|
24943
|
+
/** One stored row by id, or `null` (absent, unreadable, or a failed read). */
|
|
24944
|
+
async readRow(id) {
|
|
24945
|
+
try {
|
|
24946
|
+
const row = await this.store.get.query({
|
|
24947
|
+
collection: NC_OUTBOX_COLLECTION,
|
|
24948
|
+
key: id
|
|
24949
|
+
});
|
|
24950
|
+
return isStoredRow(row) ? rowToEntry$1(id, row) : null;
|
|
24951
|
+
} catch (err) {
|
|
24952
|
+
this.logger.debug("outbox cold-horizon dedup read failed", { meta: {
|
|
24953
|
+
id,
|
|
24954
|
+
error: String(err)
|
|
24955
|
+
} });
|
|
24956
|
+
return null;
|
|
24957
|
+
}
|
|
24958
|
+
}
|
|
24959
|
+
/**
|
|
24775
24960
|
* Does a row already exist under this dedup id?
|
|
24776
24961
|
*
|
|
24777
24962
|
* Asked ONLY while {@link horizonLoaded} is false. {@link persist} writes
|
|
@@ -24865,6 +25050,10 @@ var OUTBOX_RECORD_KINDS = new Set([
|
|
|
24865
25050
|
function isOutboxRecordKind(x) {
|
|
24866
25051
|
return typeof x === "string" && OUTBOX_RECORD_KINDS.has(x);
|
|
24867
25052
|
}
|
|
25053
|
+
/** `settingsStore.get` answers `unknown` — a guard, never a cast. */
|
|
25054
|
+
function isStoredRow(x) {
|
|
25055
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
25056
|
+
}
|
|
24868
25057
|
/** A stored row back to an entry — a guard, never a cast. Exported for the
|
|
24869
25058
|
* specs that read the store a centre wrote to. */
|
|
24870
25059
|
function rowToEntry$1(id, data) {
|
|
@@ -33231,7 +33420,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
33231
33420
|
...deps.dispatcher,
|
|
33232
33421
|
logger: this.logger.child("dispatch"),
|
|
33233
33422
|
texts: this.texts,
|
|
33234
|
-
getMediaForOwner: async (ownerKind, ownerId) => {
|
|
33423
|
+
getMediaForOwner: async (ownerKind, ownerId, eventScope) => {
|
|
33235
33424
|
const held = isSyntheticId(ownerId) ? this.syntheticMedia.get(ownerId) : void 0;
|
|
33236
33425
|
if (held !== void 0) return held.files;
|
|
33237
33426
|
const shelved = isStillShelfId(ownerId) ? this.stills?.get(ownerId) : void 0;
|
|
@@ -33240,7 +33429,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
33240
33429
|
if (linked !== void 0) return linked;
|
|
33241
33430
|
const departed = ownerKind === "track" && this.departureFallbacks.has(ownerId);
|
|
33242
33431
|
const owner = departed ? await this.resolveDepartedTrack(ownerId) : ownerId;
|
|
33243
|
-
const files = await deps.dispatcher.getMediaForOwner(ownerKind, owner);
|
|
33432
|
+
const files = await deps.dispatcher.getMediaForOwner(ownerKind, owner, eventScope);
|
|
33244
33433
|
if (files.length > 0 || !departed) return files;
|
|
33245
33434
|
return this.departureFallbackMedia(ownerId) ?? files;
|
|
33246
33435
|
},
|
|
@@ -35387,6 +35576,8 @@ var NotificationCenter = class NotificationCenter {
|
|
|
35387
35576
|
const dropped = await this.outbox.supersede({
|
|
35388
35577
|
ruleId: source.ruleId,
|
|
35389
35578
|
recordId: source.recordId,
|
|
35579
|
+
deviceId: source.deviceId,
|
|
35580
|
+
recordAt: source.timestamp,
|
|
35390
35581
|
targetIds: [...combined]
|
|
35391
35582
|
});
|
|
35392
35583
|
this.logger.info("alarm notification combined with the rule that triggered it", {
|
|
@@ -42429,6 +42620,280 @@ function parseDebugDeviceIds(url) {
|
|
|
42429
42620
|
return out;
|
|
42430
42621
|
}
|
|
42431
42622
|
//#endregion
|
|
42623
|
+
//#region src/pipeline-analytics/store/tracking-tape-index-store.ts
|
|
42624
|
+
/**
|
|
42625
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
42626
|
+
* write="one row per PROMOTED tracking-tape window (`debug.tape.promote`);
|
|
42627
|
+
* updated when the operator marks it reviewed or releases its pins;
|
|
42628
|
+
* deleted together with its file by `debug.tape.delete`."
|
|
42629
|
+
* retention="NOT an age clock and NOT track retention — a promoted window is
|
|
42630
|
+
* history being labelled (D681). Bounded by the promotion budget
|
|
42631
|
+
* (MAX_PROMOTED_TAPES = 60, 1 GiB): a promotion beyond it is REFUSED,
|
|
42632
|
+
* never satisfied by evicting another row."
|
|
42633
|
+
*/
|
|
42634
|
+
var TRACKING_TAPES_COLLECTION = "pipeline-analytics:tracking-tapes";
|
|
42635
|
+
var TRACKING_TAPES_COLUMNS = [
|
|
42636
|
+
{
|
|
42637
|
+
name: "id",
|
|
42638
|
+
type: "TEXT",
|
|
42639
|
+
primaryKey: true,
|
|
42640
|
+
notNull: true
|
|
42641
|
+
},
|
|
42642
|
+
{
|
|
42643
|
+
name: "deviceId",
|
|
42644
|
+
type: "INTEGER",
|
|
42645
|
+
notNull: true
|
|
42646
|
+
},
|
|
42647
|
+
{
|
|
42648
|
+
name: "scenario",
|
|
42649
|
+
type: "TEXT",
|
|
42650
|
+
notNull: true
|
|
42651
|
+
},
|
|
42652
|
+
{
|
|
42653
|
+
name: "note",
|
|
42654
|
+
type: "TEXT"
|
|
42655
|
+
},
|
|
42656
|
+
{
|
|
42657
|
+
name: "lighting",
|
|
42658
|
+
type: "TEXT",
|
|
42659
|
+
notNull: true
|
|
42660
|
+
},
|
|
42661
|
+
{
|
|
42662
|
+
name: "fromMs",
|
|
42663
|
+
type: "INTEGER",
|
|
42664
|
+
notNull: true
|
|
42665
|
+
},
|
|
42666
|
+
{
|
|
42667
|
+
name: "toMs",
|
|
42668
|
+
type: "INTEGER",
|
|
42669
|
+
notNull: true
|
|
42670
|
+
},
|
|
42671
|
+
{
|
|
42672
|
+
name: "state",
|
|
42673
|
+
type: "TEXT",
|
|
42674
|
+
notNull: true
|
|
42675
|
+
},
|
|
42676
|
+
{
|
|
42677
|
+
name: "frames",
|
|
42678
|
+
type: "INTEGER",
|
|
42679
|
+
notNull: true
|
|
42680
|
+
},
|
|
42681
|
+
{
|
|
42682
|
+
name: "skips",
|
|
42683
|
+
type: "INTEGER",
|
|
42684
|
+
notNull: true
|
|
42685
|
+
},
|
|
42686
|
+
{
|
|
42687
|
+
name: "gaps",
|
|
42688
|
+
type: "INTEGER",
|
|
42689
|
+
notNull: true
|
|
42690
|
+
},
|
|
42691
|
+
{
|
|
42692
|
+
name: "bytes",
|
|
42693
|
+
type: "INTEGER",
|
|
42694
|
+
notNull: true
|
|
42695
|
+
},
|
|
42696
|
+
{
|
|
42697
|
+
name: "reviewed",
|
|
42698
|
+
type: "BOOLEAN",
|
|
42699
|
+
notNull: true
|
|
42700
|
+
},
|
|
42701
|
+
{
|
|
42702
|
+
name: "trackIds",
|
|
42703
|
+
type: "TEXT",
|
|
42704
|
+
notNull: true
|
|
42705
|
+
},
|
|
42706
|
+
{
|
|
42707
|
+
name: "createdAt",
|
|
42708
|
+
type: "INTEGER",
|
|
42709
|
+
notNull: true
|
|
42710
|
+
},
|
|
42711
|
+
{
|
|
42712
|
+
name: "releasedAt",
|
|
42713
|
+
type: "INTEGER"
|
|
42714
|
+
},
|
|
42715
|
+
{
|
|
42716
|
+
name: "pinsSettled",
|
|
42717
|
+
type: "BOOLEAN",
|
|
42718
|
+
notNull: true
|
|
42719
|
+
}
|
|
42720
|
+
];
|
|
42721
|
+
var TrackingTapeRowSchema = require_clip_model_registry.object({
|
|
42722
|
+
id: require_clip_model_registry.string(),
|
|
42723
|
+
deviceId: require_clip_model_registry.number().int(),
|
|
42724
|
+
scenario: require_clip_model_registry.string(),
|
|
42725
|
+
note: require_clip_model_registry.string().nullable(),
|
|
42726
|
+
lighting: TapeLightingSchema,
|
|
42727
|
+
fromMs: require_clip_model_registry.number().int(),
|
|
42728
|
+
toMs: require_clip_model_registry.number().int(),
|
|
42729
|
+
state: require_clip_model_registry._enum(["complete", "incomplete"]),
|
|
42730
|
+
frames: require_clip_model_registry.number().int(),
|
|
42731
|
+
skips: require_clip_model_registry.number().int(),
|
|
42732
|
+
gaps: require_clip_model_registry.number().int(),
|
|
42733
|
+
bytes: require_clip_model_registry.number().int(),
|
|
42734
|
+
reviewed: require_clip_model_registry.boolean(),
|
|
42735
|
+
trackIds: require_clip_model_registry.array(require_clip_model_registry.string()),
|
|
42736
|
+
createdAt: require_clip_model_registry.number().int(),
|
|
42737
|
+
releasedAt: require_clip_model_registry.number().int().nullable(),
|
|
42738
|
+
pinsSettled: require_clip_model_registry.boolean()
|
|
42739
|
+
});
|
|
42740
|
+
var TrackIdsSchema = require_clip_model_registry.array(require_clip_model_registry.string());
|
|
42741
|
+
/**
|
|
42742
|
+
* True when the SQLite settings backend would hand `text` back DECODED: it
|
|
42743
|
+
* parses any TEXT that starts with `[` or `{` and is valid JSON
|
|
42744
|
+
* (`decodeColumnValue` decodes by content, not by declared type). Free text
|
|
42745
|
+
* like that cannot round-trip through a TEXT column (follow-up 617, review M2).
|
|
42746
|
+
*/
|
|
42747
|
+
function decodesAsJson(text) {
|
|
42748
|
+
if (!(text.startsWith("[") || text.startsWith("{"))) return false;
|
|
42749
|
+
try {
|
|
42750
|
+
JSON.parse(text);
|
|
42751
|
+
return true;
|
|
42752
|
+
} catch {
|
|
42753
|
+
return false;
|
|
42754
|
+
}
|
|
42755
|
+
}
|
|
42756
|
+
/**
|
|
42757
|
+
* Free text (`scenario`, `note`) as the column hands it back: a value the
|
|
42758
|
+
* backend decoded is re-encoded, so a row written before `debug.tape.promote`
|
|
42759
|
+
* refused such text never vanishes (its whitespace may differ). Anything else
|
|
42760
|
+
* goes to the schema as it came.
|
|
42761
|
+
*/
|
|
42762
|
+
function freeText(raw) {
|
|
42763
|
+
return raw !== null && typeof raw === "object" ? JSON.stringify(raw) : raw;
|
|
42764
|
+
}
|
|
42765
|
+
/**
|
|
42766
|
+
* `trackIds` as the column hands it back. The SQLite backend decodes a TEXT
|
|
42767
|
+
* that starts with `[` on read (`decodeColumnValue`), so the store sees the
|
|
42768
|
+
* ARRAY it wrote; a string is parsed here only for a backend (or an old row)
|
|
42769
|
+
* that hands the text back as written. `JSON.parse(String(array))` threw on the
|
|
42770
|
+
* decoded array and every promoted row was skipped as malformed (follow-up 617).
|
|
42771
|
+
* `null` = unreadable, absent included (the column is NOT NULL, so absence is
|
|
42772
|
+
* corruption) — the row is reported, never repaired.
|
|
42773
|
+
*/
|
|
42774
|
+
function decodeTrackIds(raw) {
|
|
42775
|
+
if (raw === void 0 || raw === null) return null;
|
|
42776
|
+
if (typeof raw !== "string") return raw;
|
|
42777
|
+
try {
|
|
42778
|
+
return JSON.parse(raw);
|
|
42779
|
+
} catch {
|
|
42780
|
+
return null;
|
|
42781
|
+
}
|
|
42782
|
+
}
|
|
42783
|
+
var TrackingTapeIndexStore = class {
|
|
42784
|
+
store;
|
|
42785
|
+
logger;
|
|
42786
|
+
constructor(deps) {
|
|
42787
|
+
this.store = deps.store;
|
|
42788
|
+
this.logger = deps.logger;
|
|
42789
|
+
}
|
|
42790
|
+
static async declare(store) {
|
|
42791
|
+
await store.declareCollection.mutate({
|
|
42792
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42793
|
+
columns: [...TRACKING_TAPES_COLUMNS],
|
|
42794
|
+
indexes: [{
|
|
42795
|
+
name: "idx_trackingtapes_device_created",
|
|
42796
|
+
columns: ["deviceId", "createdAt"]
|
|
42797
|
+
}]
|
|
42798
|
+
});
|
|
42799
|
+
}
|
|
42800
|
+
async insert(row) {
|
|
42801
|
+
const { id, trackIds, ...rest } = TrackingTapeRowSchema.parse(row);
|
|
42802
|
+
await this.store.insert.mutate({
|
|
42803
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42804
|
+
record: {
|
|
42805
|
+
id,
|
|
42806
|
+
data: {
|
|
42807
|
+
...rest,
|
|
42808
|
+
trackIds: JSON.stringify(trackIds)
|
|
42809
|
+
}
|
|
42810
|
+
}
|
|
42811
|
+
});
|
|
42812
|
+
}
|
|
42813
|
+
async get(id) {
|
|
42814
|
+
const first = (await this.store.query.query({
|
|
42815
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42816
|
+
filter: {
|
|
42817
|
+
where: { id },
|
|
42818
|
+
limit: 1
|
|
42819
|
+
}
|
|
42820
|
+
}))[0];
|
|
42821
|
+
return first === void 0 ? null : this.parse(first.id, first.data);
|
|
42822
|
+
}
|
|
42823
|
+
async list(deviceId) {
|
|
42824
|
+
const rows = await this.store.query.query({
|
|
42825
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42826
|
+
filter: {
|
|
42827
|
+
...deviceId !== void 0 ? { where: { deviceId } } : {},
|
|
42828
|
+
orderBy: {
|
|
42829
|
+
field: "createdAt",
|
|
42830
|
+
direction: "desc"
|
|
42831
|
+
},
|
|
42832
|
+
limit: 1e3
|
|
42833
|
+
}
|
|
42834
|
+
});
|
|
42835
|
+
const out = [];
|
|
42836
|
+
for (const r of rows) {
|
|
42837
|
+
const parsed = this.parse(r.id, r.data);
|
|
42838
|
+
if (parsed !== null) out.push(parsed);
|
|
42839
|
+
}
|
|
42840
|
+
return out;
|
|
42841
|
+
}
|
|
42842
|
+
async setReviewed(id, reviewed) {
|
|
42843
|
+
await this.store.update.mutate({
|
|
42844
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42845
|
+
id,
|
|
42846
|
+
data: { reviewed }
|
|
42847
|
+
});
|
|
42848
|
+
}
|
|
42849
|
+
/** Release AND unsettle in one row write: the unpins the release implies
|
|
42850
|
+
* are owed from this moment, whether or not the caller lives to make them. */
|
|
42851
|
+
async setReleased(id, atMs) {
|
|
42852
|
+
await this.store.update.mutate({
|
|
42853
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42854
|
+
id,
|
|
42855
|
+
data: {
|
|
42856
|
+
releasedAt: atMs,
|
|
42857
|
+
pinsSettled: false
|
|
42858
|
+
}
|
|
42859
|
+
});
|
|
42860
|
+
}
|
|
42861
|
+
async setPinsSettled(id, settled) {
|
|
42862
|
+
await this.store.update.mutate({
|
|
42863
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42864
|
+
id,
|
|
42865
|
+
data: { pinsSettled: settled }
|
|
42866
|
+
});
|
|
42867
|
+
}
|
|
42868
|
+
async remove(id) {
|
|
42869
|
+
await this.store.delete.mutate({
|
|
42870
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42871
|
+
key: id
|
|
42872
|
+
});
|
|
42873
|
+
}
|
|
42874
|
+
parse(id, data) {
|
|
42875
|
+
const ids = TrackIdsSchema.safeParse(decodeTrackIds(data["trackIds"]));
|
|
42876
|
+
const parsed = TrackingTapeRowSchema.safeParse({
|
|
42877
|
+
id,
|
|
42878
|
+
...data,
|
|
42879
|
+
scenario: freeText(data["scenario"]),
|
|
42880
|
+
note: freeText(data["note"] ?? null),
|
|
42881
|
+
releasedAt: data["releasedAt"] ?? null,
|
|
42882
|
+
trackIds: ids.success ? ids.data : null
|
|
42883
|
+
});
|
|
42884
|
+
if (parsed.success) return parsed.data;
|
|
42885
|
+
this.logger.warn("tracking tape index: skipped a malformed row", {
|
|
42886
|
+
meta: {
|
|
42887
|
+
id,
|
|
42888
|
+
issues: parsed.error.issues.map((i) => i.path.join(".")),
|
|
42889
|
+
trackIdsType: typeof data["trackIds"]
|
|
42890
|
+
},
|
|
42891
|
+
...typeof data["deviceId"] === "number" ? { tags: { deviceId: data["deviceId"] } } : {}
|
|
42892
|
+
});
|
|
42893
|
+
return null;
|
|
42894
|
+
}
|
|
42895
|
+
};
|
|
42896
|
+
//#endregion
|
|
42432
42897
|
//#region src/pipeline-analytics/debug/tape-actions.ts
|
|
42433
42898
|
/**
|
|
42434
42899
|
* `debug.tape.*` — the tracking-tape surface (D681, spec §3.3). Admin,
|
|
@@ -42459,12 +42924,19 @@ var TapeStatusRowSchema = require_clip_model_registry.object({
|
|
|
42459
42924
|
var TapeStatusInputSchema = require_clip_model_registry.object({ deviceIds: require_clip_model_registry.array(require_clip_model_registry.number().int()).optional() });
|
|
42460
42925
|
var TapeStatusOutputSchema = require_clip_model_registry.object({ cameras: require_clip_model_registry.array(TapeStatusRowSchema).readonly() });
|
|
42461
42926
|
var TapeLightingInputSchema = require_clip_model_registry._enum(["day", "ir"]);
|
|
42927
|
+
/**
|
|
42928
|
+
* Free text the settings store would hand back decoded is refused by name
|
|
42929
|
+
* (follow-up 617, review M2): whole JSON array/object text in a TEXT column
|
|
42930
|
+
* comes back as an array or object, and the index could not read the row.
|
|
42931
|
+
*/
|
|
42932
|
+
var NOT_JSON_TEXT = "must not be JSON array/object text — the settings store would hand it back decoded";
|
|
42933
|
+
var notJsonText = (s) => !decodesAsJson(s);
|
|
42462
42934
|
var PromoteInputSchema = require_clip_model_registry.object({
|
|
42463
42935
|
deviceId: require_clip_model_registry.number().int().positive(),
|
|
42464
42936
|
fromMs: require_clip_model_registry.number().int(),
|
|
42465
42937
|
toMs: require_clip_model_registry.number().int(),
|
|
42466
|
-
scenario: require_clip_model_registry.string(),
|
|
42467
|
-
note: require_clip_model_registry.string().max(500).optional(),
|
|
42938
|
+
scenario: require_clip_model_registry.string().refine(notJsonText, { message: NOT_JSON_TEXT }),
|
|
42939
|
+
note: require_clip_model_registry.string().max(500).refine(notJsonText, { message: NOT_JSON_TEXT }).optional(),
|
|
42468
42940
|
lighting: TapeLightingInputSchema.optional()
|
|
42469
42941
|
});
|
|
42470
42942
|
var PromoteOutputSchema = require_clip_model_registry.discriminatedUnion("ok", [require_clip_model_registry.object({
|
|
@@ -50927,6 +51399,17 @@ function pairRiders(detections) {
|
|
|
50927
51399
|
* (D438) no frame did, and a newcomer on the stale prediction took the id.
|
|
50928
51400
|
*/
|
|
50929
51401
|
var MAX_PREDICTION_MS = 1e3;
|
|
51402
|
+
/**
|
|
51403
|
+
* A frame more than `maxMissedMs` behind the latest is a CLOCK STEP only as
|
|
51404
|
+
* the K-th such frame in a row, each in order with the one before (follow-up
|
|
51405
|
+
* 617, fix round 3; D49: a read that would destroy work needs another to
|
|
51406
|
+
* agree). A step ends every track on the camera; one stray timestamp — a
|
|
51407
|
+
* backlog spike 4.5 s late — or two runner clocks delivering interleaved
|
|
51408
|
+
* (a camera migrating between runners) must not. Three is the least that a
|
|
51409
|
+
* single late frame plus an interleaved partner cannot reach by chance; the
|
|
51410
|
+
* cost of a real step is K−1 dropped frames (~260 ms at 7.7 fps).
|
|
51411
|
+
*/
|
|
51412
|
+
var CLOCK_STEP_CONFIRM_FRAMES = 3;
|
|
50930
51413
|
/** Confident boxes kept on a tracklet's path (the old tracker's bound). */
|
|
50931
51414
|
var MAX_PATH_LENGTH = 300;
|
|
50932
51415
|
/** Swap test: crossed pairs cheaper than this over the chosen pairs are doubt. */
|
|
@@ -51732,6 +52215,35 @@ function linkReach(from, gapMs) {
|
|
|
51732
52215
|
return diag * Math.min(LINK_REACH_MAX_DIAGONALS, Math.max(LINK_REACH_MIN_DIAGONALS, LINK_REACH_DIAGONALS_PER_SEC * gapMs / 1e3));
|
|
51733
52216
|
}
|
|
51734
52217
|
var arcKey = (from, to) => `${from}\u0000${to}`;
|
|
52218
|
+
/**
|
|
52219
|
+
* The rejections that mean "geometry or appearance said no to a plausible
|
|
52220
|
+
* continuation" of a confirmed predecessor — a track the operator sees break
|
|
52221
|
+
* (follow-up 617, F5). Every one of them is past `hardRejection`, so the
|
|
52222
|
+
* predecessor is confirmed. NOT here (fix round 1): `unconfirmed`;
|
|
52223
|
+
* `has-successor` (continued elsewhere); `concurrent` and `identity-conflict`
|
|
52224
|
+
* (two subjects — the normal case, and the most frequent); `dropped` (an
|
|
52225
|
+
* operator delete or a refused chain).
|
|
52226
|
+
*/
|
|
52227
|
+
var BREAK_REJECTIONS = new Set([
|
|
52228
|
+
"reach",
|
|
52229
|
+
"area",
|
|
52230
|
+
"appearance",
|
|
52231
|
+
"contested",
|
|
52232
|
+
"cost"
|
|
52233
|
+
]);
|
|
52234
|
+
/** The confirmed predecessors whose link a decision rejected as a break (F5). */
|
|
52235
|
+
function confirmedPredecessorRejections(candidates) {
|
|
52236
|
+
const out = [];
|
|
52237
|
+
for (const c of candidates ?? []) {
|
|
52238
|
+
if (c.rejected === void 0 || !BREAK_REJECTIONS.has(c.rejected)) continue;
|
|
52239
|
+
out.push({
|
|
52240
|
+
from: c.from,
|
|
52241
|
+
rejected: c.rejected,
|
|
52242
|
+
gapMs: c.gapMs
|
|
52243
|
+
});
|
|
52244
|
+
}
|
|
52245
|
+
return out;
|
|
52246
|
+
}
|
|
51735
52247
|
function arcFacts(a) {
|
|
51736
52248
|
return {
|
|
51737
52249
|
from: a.from.id,
|
|
@@ -51809,6 +52321,26 @@ var DeferredLinker = class {
|
|
|
51809
52321
|
if (seen === void 0) this.names.set(p.id, new Set([p.identity]));
|
|
51810
52322
|
else seen.add(p.identity);
|
|
51811
52323
|
}
|
|
52324
|
+
/**
|
|
52325
|
+
* A clock step (follow-up 617, fix round 2): every piece still undecided is
|
|
52326
|
+
* decided `new`, so nothing that existed before the step can be absorbed
|
|
52327
|
+
* across it — whatever was ready was committed just before. The facade
|
|
52328
|
+
* rebases its clock past `LINK_MAX_GAP_MS`, so no later piece can continue
|
|
52329
|
+
* one of these either. An extra id, never a mix-up.
|
|
52330
|
+
*/
|
|
52331
|
+
closeUndecided(now, pieces) {
|
|
52332
|
+
for (const p of pieces) this.accept(p, now);
|
|
52333
|
+
for (const p of this.pieces.values()) {
|
|
52334
|
+
if (this.outcomes.has(p.id)) continue;
|
|
52335
|
+
this.decide(p.id, "new");
|
|
52336
|
+
this.observer?.({
|
|
52337
|
+
t: now,
|
|
52338
|
+
kind: "new",
|
|
52339
|
+
to: p.id,
|
|
52340
|
+
reason: "clock-step"
|
|
52341
|
+
});
|
|
52342
|
+
}
|
|
52343
|
+
}
|
|
51812
52344
|
isTailFinal(id, now, isLive) {
|
|
51813
52345
|
const root = this.rootOf(id);
|
|
51814
52346
|
if (this.pieces.has(root) && !this.outcomes.has(root)) return false;
|
|
@@ -52559,7 +53091,15 @@ var Tracklet = class {
|
|
|
52559
53091
|
h: this.assocBox.h
|
|
52560
53092
|
};
|
|
52561
53093
|
}
|
|
53094
|
+
/**
|
|
53095
|
+
* A confident sighting. One OLDER than the last (`t < lastSeenAt` — ~1 % of
|
|
53096
|
+
* frames reach the tracker out of capture order, up to 1.2 s back) is
|
|
53097
|
+
* `stale`: it changes nothing. A rewound tracklet would be measured from a
|
|
53098
|
+
* box its subject left a second ago, and L2 refused a link by reach on
|
|
53099
|
+
* exactly that (follow-up 617, F1). The caller reports it.
|
|
53100
|
+
*/
|
|
52562
53101
|
observeConfident(det, t, faceBearing) {
|
|
53102
|
+
if (t < this.lastSeenAt) return "stale";
|
|
52563
53103
|
const dt = t - this.lastSeenAt;
|
|
52564
53104
|
if (dt > 0) {
|
|
52565
53105
|
const from = centreOf(this.lastBox);
|
|
@@ -52571,6 +53111,7 @@ var Tracklet = class {
|
|
|
52571
53111
|
this.emitIntervalMs = dt;
|
|
52572
53112
|
}
|
|
52573
53113
|
this.recordConfident(det, t, faceBearing);
|
|
53114
|
+
return "recorded";
|
|
52574
53115
|
}
|
|
52575
53116
|
observeLowScore(box, t) {
|
|
52576
53117
|
this.assocBox = box;
|
|
@@ -52672,6 +53213,15 @@ var TrackletTracker = class {
|
|
|
52672
53213
|
demoteSilent(t) {
|
|
52673
53214
|
for (const tr of this.live.values()) if (tr.mode === "active" && t - tr.lastSeenAt > MAX_PREDICTION_MS) tr.mode = "coasting";
|
|
52674
53215
|
}
|
|
53216
|
+
/** Ends every live tracklet at its own last sighting (a clock step); the ids it ended. */
|
|
53217
|
+
endAll(reason) {
|
|
53218
|
+
const ended = [];
|
|
53219
|
+
for (const tr of [...this.live.values()]) {
|
|
53220
|
+
ended.push(tr.id);
|
|
53221
|
+
this.finish(tr, tr.lastSeenAt, reason);
|
|
53222
|
+
}
|
|
53223
|
+
return ended;
|
|
53224
|
+
}
|
|
52675
53225
|
/** Silence: end what the coast budget no longer covers. */
|
|
52676
53226
|
expire(now) {
|
|
52677
53227
|
for (const tr of [...this.live.values()]) if (now - tr.lastSeenAt > this.config.maxMissedMs) this.finish(tr, tr.lastSeenAt, "missed");
|
|
@@ -52762,12 +53312,12 @@ var TrackletTracker = class {
|
|
|
52762
53312
|
const d = frame.detections[pair.detection];
|
|
52763
53313
|
if (tr === void 0 || d === void 0) return;
|
|
52764
53314
|
frame.used.add(pair.detection);
|
|
53315
|
+
if (tr.observeConfident(d, frame.t, frame.ctx.faceBearing?.has(d.bbox) === true) === "stale") return;
|
|
52765
53316
|
tr.mode = "active";
|
|
52766
53317
|
if (d.class !== tr.klass) this.refuse({
|
|
52767
53318
|
reason: "cross-class-absorbed",
|
|
52768
53319
|
against: tr
|
|
52769
53320
|
}, d);
|
|
52770
|
-
tr.observeConfident(d, frame.t, frame.ctx.faceBearing?.has(d.bbox) === true);
|
|
52771
53321
|
if (pair.contested) {
|
|
52772
53322
|
tr.contestedHits += 1;
|
|
52773
53323
|
tr.contestedSince ??= frame.t;
|
|
@@ -52956,27 +53506,151 @@ var TrackerV2 = class {
|
|
|
52956
53506
|
l1;
|
|
52957
53507
|
l2;
|
|
52958
53508
|
pending = [];
|
|
53509
|
+
/**
|
|
53510
|
+
* Internal time = the caller's frame time + `offset`. Zero until a clock
|
|
53511
|
+
* step; each step moves it so the internal clock never goes back and
|
|
53512
|
+
* resumes past `LINK_MAX_GAP_MS` of silence (fix round 2, C1). L1, L2, their
|
|
53513
|
+
* facts and prunes run on internal time; fusions and `frameClock()` are
|
|
53514
|
+
* handed back in the caller's clock.
|
|
53515
|
+
*/
|
|
53516
|
+
offset = 0;
|
|
52959
53517
|
lastT = 0;
|
|
53518
|
+
/** The latest FRAME time taken (internal) — never the sweep's extrapolated `now`. */
|
|
53519
|
+
lastFrameT = Number.NEGATIVE_INFINITY;
|
|
53520
|
+
observers;
|
|
53521
|
+
/** Caller times of the consecutive frames past the stale bound so far — the
|
|
53522
|
+
* run a clock step needs `CLOCK_STEP_CONFIRM_FRAMES` of (fix round 3). */
|
|
53523
|
+
stepRun = [];
|
|
52960
53524
|
constructor(config, deps) {
|
|
52961
53525
|
this.config = config;
|
|
52962
|
-
this.
|
|
53526
|
+
this.observers = deps.observers ?? {};
|
|
53527
|
+
const out = this.observers;
|
|
53528
|
+
const internal = {
|
|
53529
|
+
...out.refusal !== void 0 ? { refusal: out.refusal } : {},
|
|
53530
|
+
ended: (f) => out.ended?.({
|
|
53531
|
+
...f,
|
|
53532
|
+
at: f.at - this.offset
|
|
53533
|
+
}),
|
|
53534
|
+
split: (f) => out.split?.({
|
|
53535
|
+
...f,
|
|
53536
|
+
t: f.t - this.offset
|
|
53537
|
+
})
|
|
53538
|
+
};
|
|
53539
|
+
this.l1 = new TrackletTracker(config, deps.mintTrackId, internal);
|
|
52963
53540
|
this.l2 = new DeferredLinker({
|
|
52964
53541
|
maxMissedMs: config.maxMissedMs,
|
|
52965
53542
|
appearanceResolvesAmbiguity: config.appearanceResolvesAmbiguity
|
|
52966
|
-
},
|
|
53543
|
+
}, (f) => out.link?.({
|
|
53544
|
+
...f,
|
|
53545
|
+
t: f.t - this.offset
|
|
53546
|
+
}));
|
|
52967
53547
|
}
|
|
52968
|
-
update(detections,
|
|
53548
|
+
update(detections, frameT, ctx) {
|
|
53549
|
+
const order = this.classifyFrame(frameT);
|
|
53550
|
+
if (order === "out-of-order") return this.drop(frameT, detections.length);
|
|
53551
|
+
let t = frameT + this.offset;
|
|
53552
|
+
if (order === "clock-step") t = this.clockStep(frameT, this.lastFrameT - t);
|
|
53553
|
+
this.stepRun = [];
|
|
53554
|
+
this.lastFrameT = t;
|
|
52969
53555
|
this.lastT = Math.max(this.lastT, t);
|
|
52970
53556
|
const step = this.l1.step(detections, t, ctx);
|
|
52971
53557
|
this.link(this.lastT);
|
|
52972
53558
|
return step.emissions.map((e) => this.emit(e, t));
|
|
52973
53559
|
}
|
|
52974
53560
|
/**
|
|
53561
|
+
* A frame stamped before the latest one already taken (follow-up 617, F1;
|
|
53562
|
+
* ~1 % of frames, up to 1.2 s back). It is decided ONCE, for the whole
|
|
53563
|
+
* frame (fix round 1, H1): matched against the tracklets' NEWEST boxes, a
|
|
53564
|
+
* stale box can land on another subject — a walker's old box on the walker
|
|
53565
|
+
* behind — and be emitted under that id. So nothing binds, spawns, splits or
|
|
53566
|
+
* is demoted on it; the confirmed tracklets are emitted as on a frame that
|
|
53567
|
+
* matched nothing (no sighting), and one fact says the frame was dropped.
|
|
53568
|
+
*/
|
|
53569
|
+
/**
|
|
53570
|
+
* How `update` will take a frame at `frameT` — without taking it. The
|
|
53571
|
+
* processor asks first (fix round 3, MEDIUM-2), so a dropped frame never
|
|
53572
|
+
* reaches the stationary gate, the zones or the event path.
|
|
53573
|
+
* - `in-order`: not behind the latest frame taken;
|
|
53574
|
+
* - `out-of-order`: behind it — within `maxMissedMs` (disorder, ≤ 1.2 s
|
|
53575
|
+
* measured), or further back but not yet the `CLOCK_STEP_CONFIRM_FRAMES`-th
|
|
53576
|
+
* such frame in a row;
|
|
53577
|
+
* - `clock-step`: the K-th consecutive frame past the bound, each in order
|
|
53578
|
+
* with the one before (within `maxMissedMs`).
|
|
53579
|
+
*/
|
|
53580
|
+
classifyFrame(frameT) {
|
|
53581
|
+
const behindMs = this.lastFrameT - (frameT + this.offset);
|
|
53582
|
+
if (!(behindMs > 0)) return "in-order";
|
|
53583
|
+
if (behindMs <= this.config.maxMissedMs) return "out-of-order";
|
|
53584
|
+
return this.runLength(frameT) >= CLOCK_STEP_CONFIRM_FRAMES ? "clock-step" : "out-of-order";
|
|
53585
|
+
}
|
|
53586
|
+
/** The past-bound run this frame would make, itself included. */
|
|
53587
|
+
runLength(frameT) {
|
|
53588
|
+
const prev = this.stepRun.at(-1);
|
|
53589
|
+
return (prev !== void 0 && frameT >= prev && frameT - prev <= this.config.maxMissedMs ? this.stepRun.length : 0) + 1;
|
|
53590
|
+
}
|
|
53591
|
+
/**
|
|
53592
|
+
* Drops a frame `classifyFrame` called `out-of-order`. A frame past the
|
|
53593
|
+
* bound extends (or restarts) the run a clock step needs; any other breaks
|
|
53594
|
+
* it. One fact per frame.
|
|
53595
|
+
*/
|
|
53596
|
+
drop(frameT, detections) {
|
|
53597
|
+
const behindMs = this.lastFrameT - (frameT + this.offset);
|
|
53598
|
+
let pendingClockStep = 0;
|
|
53599
|
+
if (behindMs > this.config.maxMissedMs) {
|
|
53600
|
+
pendingClockStep = this.runLength(frameT);
|
|
53601
|
+
this.stepRun = pendingClockStep === 1 ? [frameT] : [...this.stepRun, frameT];
|
|
53602
|
+
} else this.stepRun = [];
|
|
53603
|
+
this.observers.outOfOrder?.({
|
|
53604
|
+
t: frameT,
|
|
53605
|
+
lastFrameT: this.frameClock(),
|
|
53606
|
+
behindMs,
|
|
53607
|
+
detections,
|
|
53608
|
+
pendingClockStep
|
|
53609
|
+
});
|
|
53610
|
+
return this.l1.liveTracklets().filter((tr) => tr.confirmed).map((tracklet) => this.emit({
|
|
53611
|
+
tracklet,
|
|
53612
|
+
detection: void 0,
|
|
53613
|
+
matched: false
|
|
53614
|
+
}, this.lastFrameT));
|
|
53615
|
+
}
|
|
53616
|
+
/**
|
|
53617
|
+
* The frame clock stepped back further than `maxMissedMs` (fix round 2, C1):
|
|
53618
|
+
* not disorder — an NTP correction, or a camera moved to a runner whose clock
|
|
53619
|
+
* is behind. An unbounded gate dropped every frame until the new clock passed
|
|
53620
|
+
* the old high mark (60 s back: 461 frames, a new subject never tracked, the
|
|
53621
|
+
* departed one coasting on). Instead: commit what L2 has decided, end every
|
|
53622
|
+
* live tracklet at its own last sighting, decide every piece still undecided
|
|
53623
|
+
* `new` (no fusion crosses the step), and resume the internal clock past
|
|
53624
|
+
* `LINK_MAX_GAP_MS` of silence, so nothing later can continue anything
|
|
53625
|
+
* earlier. Returns the frame's internal time.
|
|
53626
|
+
*/
|
|
53627
|
+
clockStep(frameT, behindMs) {
|
|
53628
|
+
const lastFrameT = this.frameClock();
|
|
53629
|
+
this.link(this.lastT);
|
|
53630
|
+
const ended = this.l1.endAll("clock-step");
|
|
53631
|
+
this.l2.closeUndecided(this.lastT, this.pieces());
|
|
53632
|
+
const resumeAt = Math.max(this.lastT, this.lastFrameT) + LINK_MAX_GAP_MS + 1;
|
|
53633
|
+
this.offset = resumeAt - frameT;
|
|
53634
|
+
this.lastT = resumeAt;
|
|
53635
|
+
this.observers.clockStep?.({
|
|
53636
|
+
t: frameT,
|
|
53637
|
+
lastFrameT,
|
|
53638
|
+
behindMs,
|
|
53639
|
+
ended
|
|
53640
|
+
});
|
|
53641
|
+
return resumeAt;
|
|
53642
|
+
}
|
|
53643
|
+
/** The latest frame time taken, in the caller's clock (rebased by a clock step). */
|
|
53644
|
+
frameClock() {
|
|
53645
|
+
return this.lastFrameT - this.offset;
|
|
53646
|
+
}
|
|
53647
|
+
/**
|
|
52975
53648
|
* The close sweep's entry on a camera whose frames stopped (D438): end the
|
|
52976
53649
|
* tracklets the coast budget no longer covers, THEN run L2 (review C2).
|
|
52977
53650
|
* `now` is FRAME time (the processor converts wall clock, Task 11).
|
|
52978
53651
|
*/
|
|
52979
|
-
advance(
|
|
53652
|
+
advance(frameNow) {
|
|
53653
|
+
const now = frameNow + this.offset;
|
|
52980
53654
|
this.lastT = Math.max(this.lastT, now);
|
|
52981
53655
|
this.l1.expire(now);
|
|
52982
53656
|
this.link(now);
|
|
@@ -52992,6 +53666,7 @@ var TrackerV2 = class {
|
|
|
52992
53666
|
markSuppressed(id) {
|
|
52993
53667
|
this.l1.markSuppressed(id);
|
|
52994
53668
|
}
|
|
53669
|
+
/** In the caller's clock (translated when each was committed, LOW-5). */
|
|
52995
53670
|
drainFusions() {
|
|
52996
53671
|
const out = this.pending;
|
|
52997
53672
|
this.pending = [];
|
|
@@ -53004,8 +53679,8 @@ var TrackerV2 = class {
|
|
|
53004
53679
|
isLive(id) {
|
|
53005
53680
|
return this.l2.membersOf(this.l2.rootOf(id)).some((m) => this.isLiveTracklet(m));
|
|
53006
53681
|
}
|
|
53007
|
-
isTailFinal(id,
|
|
53008
|
-
return this.l2.isTailFinal(id,
|
|
53682
|
+
isTailFinal(id, frameNow) {
|
|
53683
|
+
return this.l2.isTailFinal(id, frameNow + this.offset, (m) => this.isLiveTracklet(m));
|
|
53009
53684
|
}
|
|
53010
53685
|
/**
|
|
53011
53686
|
* Ends every live member of the chain. `stationary` (round 4): the
|
|
@@ -53021,9 +53696,15 @@ var TrackerV2 = class {
|
|
|
53021
53696
|
const tr = this.l1.get(id);
|
|
53022
53697
|
return tr !== void 0 && tr.endedAt === null;
|
|
53023
53698
|
}
|
|
53699
|
+
pieces() {
|
|
53700
|
+
return [...this.l1.liveTracklets(), ...this.l1.endedTracklets()].map(toPiece);
|
|
53701
|
+
}
|
|
53024
53702
|
link(now) {
|
|
53025
|
-
const
|
|
53026
|
-
|
|
53703
|
+
const offset = this.offset;
|
|
53704
|
+
for (const f of this.l2.step(now, this.pieces())) this.pending.push({
|
|
53705
|
+
...f,
|
|
53706
|
+
t: f.t - offset
|
|
53707
|
+
});
|
|
53027
53708
|
this.l1.prune(now);
|
|
53028
53709
|
this.l2.prune(now);
|
|
53029
53710
|
}
|
|
@@ -53086,7 +53767,8 @@ var NO_ENDS = {
|
|
|
53086
53767
|
missed: 0,
|
|
53087
53768
|
ambiguous: 0,
|
|
53088
53769
|
dropped: 0,
|
|
53089
|
-
stationary: 0
|
|
53770
|
+
stationary: 0,
|
|
53771
|
+
"clock-step": 0
|
|
53090
53772
|
};
|
|
53091
53773
|
function emptyStats() {
|
|
53092
53774
|
return {
|
|
@@ -53099,16 +53781,28 @@ function emptyStats() {
|
|
|
53099
53781
|
linksRefused: 0,
|
|
53100
53782
|
linksNew: 0,
|
|
53101
53783
|
linksDisagreed: 0,
|
|
53102
|
-
lowScoreHolds: 0
|
|
53784
|
+
lowScoreHolds: 0,
|
|
53785
|
+
outOfOrderFrames: 0,
|
|
53786
|
+
clockSteps: 0
|
|
53103
53787
|
};
|
|
53104
53788
|
}
|
|
53105
53789
|
/** Nothing happened in the window: no tracklet ended, no split, no L2
|
|
53106
53790
|
* decision, no low-score hold (review LOW-N1 — what the sweep stays quiet about). */
|
|
53107
53791
|
function isIdleTrackerStats(stats) {
|
|
53108
|
-
return [stats.tracklets.confirmed, stats.tracklets.unconfirmed].flatMap((c) => Object.values(c)).every((n) => n === 0) && stats.splits === 0 && stats.linksCommitted === 0 && stats.linksRefused === 0 && stats.linksNew === 0 && stats.linksDisagreed === 0 && stats.lowScoreHolds === 0;
|
|
53792
|
+
return [stats.tracklets.confirmed, stats.tracklets.unconfirmed].flatMap((c) => Object.values(c)).every((n) => n === 0) && stats.splits === 0 && stats.linksCommitted === 0 && stats.linksRefused === 0 && stats.linksNew === 0 && stats.linksDisagreed === 0 && stats.lowScoreHolds === 0 && stats.outOfOrderFrames === 0 && stats.clockSteps === 0;
|
|
53109
53793
|
}
|
|
53110
53794
|
/** Shared empty — a fold is rare and every other frame would allocate. */
|
|
53111
53795
|
var EMPTY_RIDER_FOLDS = [];
|
|
53796
|
+
/** The tape observation of a dropped frame: no gate verdict, nothing bound. */
|
|
53797
|
+
var DROPPED_FRAME_OBSERVATION = {
|
|
53798
|
+
gate: {
|
|
53799
|
+
strong: [],
|
|
53800
|
+
weak: [],
|
|
53801
|
+
confirmed: [],
|
|
53802
|
+
woken: []
|
|
53803
|
+
},
|
|
53804
|
+
live: []
|
|
53805
|
+
};
|
|
53112
53806
|
/** Mapping from StateAnalyzer's `ObjectState.state` values to the
|
|
53113
53807
|
* canonical TrackState enum used on tracks + events. */
|
|
53114
53808
|
function mapObjectStateToTrackState(s) {
|
|
@@ -53287,6 +53981,14 @@ var FrameProcessor = class {
|
|
|
53287
53981
|
else if (f.kind === "new") this.stats.linksNew += 1;
|
|
53288
53982
|
else if (f.kind === "disagreed") this.stats.linksDisagreed += 1;
|
|
53289
53983
|
this.trackerObservers.link?.(f);
|
|
53984
|
+
},
|
|
53985
|
+
outOfOrder: (f) => {
|
|
53986
|
+
this.stats.outOfOrderFrames += 1;
|
|
53987
|
+
this.trackerObservers.outOfOrder?.(f);
|
|
53988
|
+
},
|
|
53989
|
+
clockStep: (f) => {
|
|
53990
|
+
this.stats.clockSteps += 1;
|
|
53991
|
+
this.trackerObservers.clockStep?.(f);
|
|
53290
53992
|
}
|
|
53291
53993
|
}
|
|
53292
53994
|
});
|
|
@@ -53570,6 +54272,37 @@ var FrameProcessor = class {
|
|
|
53570
54272
|
* Zero is the expected reading; a rising one means two people stand close
|
|
53571
54273
|
* together on this camera, which is worth knowing per-camera rather than
|
|
53572
54274
|
* per-fleet. */
|
|
54275
|
+
/**
|
|
54276
|
+
* A frame `classifyFrame` called out of order: the tracker records the drop
|
|
54277
|
+
* (and any clock-step run it extends) and nothing else reads the frame. The
|
|
54278
|
+
* per-frame side tables a reader asks for afterwards are emptied, so it never
|
|
54279
|
+
* re-reads the previous frame's.
|
|
54280
|
+
*/
|
|
54281
|
+
droppedFrame(input) {
|
|
54282
|
+
this.tracker.drop(input.timestamp, input.frame.detections.length);
|
|
54283
|
+
this.lastZoneOverlaps = /* @__PURE__ */ new Map();
|
|
54284
|
+
this.lastZoneRejections = /* @__PURE__ */ new Map();
|
|
54285
|
+
this.lastLabelWriteRefusals = [];
|
|
54286
|
+
const fusions = this.tracker.drainFusions();
|
|
54287
|
+
this.applyFusions(fusions);
|
|
54288
|
+
return {
|
|
54289
|
+
deviceId: this.deviceId,
|
|
54290
|
+
timestamp: input.timestamp,
|
|
54291
|
+
frameWidth: input.frame.width,
|
|
54292
|
+
frameHeight: input.frame.height,
|
|
54293
|
+
tracked: [],
|
|
54294
|
+
objectEvents: [],
|
|
54295
|
+
appearanceEvents: [],
|
|
54296
|
+
rawTrackedDetections: [],
|
|
54297
|
+
stationaryConfirmed: [],
|
|
54298
|
+
stationaryWoken: [],
|
|
54299
|
+
stationaryContinued: 0,
|
|
54300
|
+
riderFolds: [],
|
|
54301
|
+
...input.observe === true ? { observation: DROPPED_FRAME_OBSERVATION } : {},
|
|
54302
|
+
fusions,
|
|
54303
|
+
droppedOutOfOrder: true
|
|
54304
|
+
};
|
|
54305
|
+
}
|
|
53573
54306
|
facesRefusedOutsideParent() {
|
|
53574
54307
|
return this.faceOutsideParentCount;
|
|
53575
54308
|
}
|
|
@@ -53592,6 +54325,7 @@ var FrameProcessor = class {
|
|
|
53592
54325
|
}
|
|
53593
54326
|
process(input) {
|
|
53594
54327
|
const { timestamp, frame } = input;
|
|
54328
|
+
if (this.tracker.classifyFrame(timestamp) === "out-of-order") return this.droppedFrame(input);
|
|
53595
54329
|
this.lastFrameT = timestamp;
|
|
53596
54330
|
this.lastFrameWallAt = this.wallClock();
|
|
53597
54331
|
const frameWidth = frame.width;
|
|
@@ -56343,239 +57077,6 @@ async function promoteTapeWindow(deps, input, signal) {
|
|
|
56343
57077
|
};
|
|
56344
57078
|
}
|
|
56345
57079
|
//#endregion
|
|
56346
|
-
//#region src/pipeline-analytics/store/tracking-tape-index-store.ts
|
|
56347
|
-
/**
|
|
56348
|
-
* @durable class=ledger owner=pipeline-analytics
|
|
56349
|
-
* write="one row per PROMOTED tracking-tape window (`debug.tape.promote`);
|
|
56350
|
-
* updated when the operator marks it reviewed or releases its pins;
|
|
56351
|
-
* deleted together with its file by `debug.tape.delete`."
|
|
56352
|
-
* retention="NOT an age clock and NOT track retention — a promoted window is
|
|
56353
|
-
* history being labelled (D681). Bounded by the promotion budget
|
|
56354
|
-
* (MAX_PROMOTED_TAPES = 60, 1 GiB): a promotion beyond it is REFUSED,
|
|
56355
|
-
* never satisfied by evicting another row."
|
|
56356
|
-
*/
|
|
56357
|
-
var TRACKING_TAPES_COLLECTION = "pipeline-analytics:tracking-tapes";
|
|
56358
|
-
var TRACKING_TAPES_COLUMNS = [
|
|
56359
|
-
{
|
|
56360
|
-
name: "id",
|
|
56361
|
-
type: "TEXT",
|
|
56362
|
-
primaryKey: true,
|
|
56363
|
-
notNull: true
|
|
56364
|
-
},
|
|
56365
|
-
{
|
|
56366
|
-
name: "deviceId",
|
|
56367
|
-
type: "INTEGER",
|
|
56368
|
-
notNull: true
|
|
56369
|
-
},
|
|
56370
|
-
{
|
|
56371
|
-
name: "scenario",
|
|
56372
|
-
type: "TEXT",
|
|
56373
|
-
notNull: true
|
|
56374
|
-
},
|
|
56375
|
-
{
|
|
56376
|
-
name: "note",
|
|
56377
|
-
type: "TEXT"
|
|
56378
|
-
},
|
|
56379
|
-
{
|
|
56380
|
-
name: "lighting",
|
|
56381
|
-
type: "TEXT",
|
|
56382
|
-
notNull: true
|
|
56383
|
-
},
|
|
56384
|
-
{
|
|
56385
|
-
name: "fromMs",
|
|
56386
|
-
type: "INTEGER",
|
|
56387
|
-
notNull: true
|
|
56388
|
-
},
|
|
56389
|
-
{
|
|
56390
|
-
name: "toMs",
|
|
56391
|
-
type: "INTEGER",
|
|
56392
|
-
notNull: true
|
|
56393
|
-
},
|
|
56394
|
-
{
|
|
56395
|
-
name: "state",
|
|
56396
|
-
type: "TEXT",
|
|
56397
|
-
notNull: true
|
|
56398
|
-
},
|
|
56399
|
-
{
|
|
56400
|
-
name: "frames",
|
|
56401
|
-
type: "INTEGER",
|
|
56402
|
-
notNull: true
|
|
56403
|
-
},
|
|
56404
|
-
{
|
|
56405
|
-
name: "skips",
|
|
56406
|
-
type: "INTEGER",
|
|
56407
|
-
notNull: true
|
|
56408
|
-
},
|
|
56409
|
-
{
|
|
56410
|
-
name: "gaps",
|
|
56411
|
-
type: "INTEGER",
|
|
56412
|
-
notNull: true
|
|
56413
|
-
},
|
|
56414
|
-
{
|
|
56415
|
-
name: "bytes",
|
|
56416
|
-
type: "INTEGER",
|
|
56417
|
-
notNull: true
|
|
56418
|
-
},
|
|
56419
|
-
{
|
|
56420
|
-
name: "reviewed",
|
|
56421
|
-
type: "BOOLEAN",
|
|
56422
|
-
notNull: true
|
|
56423
|
-
},
|
|
56424
|
-
{
|
|
56425
|
-
name: "trackIds",
|
|
56426
|
-
type: "TEXT",
|
|
56427
|
-
notNull: true
|
|
56428
|
-
},
|
|
56429
|
-
{
|
|
56430
|
-
name: "createdAt",
|
|
56431
|
-
type: "INTEGER",
|
|
56432
|
-
notNull: true
|
|
56433
|
-
},
|
|
56434
|
-
{
|
|
56435
|
-
name: "releasedAt",
|
|
56436
|
-
type: "INTEGER"
|
|
56437
|
-
},
|
|
56438
|
-
{
|
|
56439
|
-
name: "pinsSettled",
|
|
56440
|
-
type: "BOOLEAN",
|
|
56441
|
-
notNull: true
|
|
56442
|
-
}
|
|
56443
|
-
];
|
|
56444
|
-
var TrackingTapeRowSchema = require_clip_model_registry.object({
|
|
56445
|
-
id: require_clip_model_registry.string(),
|
|
56446
|
-
deviceId: require_clip_model_registry.number().int(),
|
|
56447
|
-
scenario: require_clip_model_registry.string(),
|
|
56448
|
-
note: require_clip_model_registry.string().nullable(),
|
|
56449
|
-
lighting: TapeLightingSchema,
|
|
56450
|
-
fromMs: require_clip_model_registry.number().int(),
|
|
56451
|
-
toMs: require_clip_model_registry.number().int(),
|
|
56452
|
-
state: require_clip_model_registry._enum(["complete", "incomplete"]),
|
|
56453
|
-
frames: require_clip_model_registry.number().int(),
|
|
56454
|
-
skips: require_clip_model_registry.number().int(),
|
|
56455
|
-
gaps: require_clip_model_registry.number().int(),
|
|
56456
|
-
bytes: require_clip_model_registry.number().int(),
|
|
56457
|
-
reviewed: require_clip_model_registry.boolean(),
|
|
56458
|
-
trackIds: require_clip_model_registry.array(require_clip_model_registry.string()),
|
|
56459
|
-
createdAt: require_clip_model_registry.number().int(),
|
|
56460
|
-
releasedAt: require_clip_model_registry.number().int().nullable(),
|
|
56461
|
-
pinsSettled: require_clip_model_registry.boolean()
|
|
56462
|
-
});
|
|
56463
|
-
var TrackIdsSchema = require_clip_model_registry.array(require_clip_model_registry.string());
|
|
56464
|
-
var TrackingTapeIndexStore = class {
|
|
56465
|
-
store;
|
|
56466
|
-
logger;
|
|
56467
|
-
constructor(deps) {
|
|
56468
|
-
this.store = deps.store;
|
|
56469
|
-
this.logger = deps.logger;
|
|
56470
|
-
}
|
|
56471
|
-
static async declare(store) {
|
|
56472
|
-
await store.declareCollection.mutate({
|
|
56473
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56474
|
-
columns: [...TRACKING_TAPES_COLUMNS],
|
|
56475
|
-
indexes: [{
|
|
56476
|
-
name: "idx_trackingtapes_device_created",
|
|
56477
|
-
columns: ["deviceId", "createdAt"]
|
|
56478
|
-
}]
|
|
56479
|
-
});
|
|
56480
|
-
}
|
|
56481
|
-
async insert(row) {
|
|
56482
|
-
const { id, trackIds, ...rest } = TrackingTapeRowSchema.parse(row);
|
|
56483
|
-
await this.store.insert.mutate({
|
|
56484
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56485
|
-
record: {
|
|
56486
|
-
id,
|
|
56487
|
-
data: {
|
|
56488
|
-
...rest,
|
|
56489
|
-
trackIds: JSON.stringify(trackIds)
|
|
56490
|
-
}
|
|
56491
|
-
}
|
|
56492
|
-
});
|
|
56493
|
-
}
|
|
56494
|
-
async get(id) {
|
|
56495
|
-
const first = (await this.store.query.query({
|
|
56496
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56497
|
-
filter: {
|
|
56498
|
-
where: { id },
|
|
56499
|
-
limit: 1
|
|
56500
|
-
}
|
|
56501
|
-
}))[0];
|
|
56502
|
-
return first === void 0 ? null : this.parse(first.id, first.data);
|
|
56503
|
-
}
|
|
56504
|
-
async list(deviceId) {
|
|
56505
|
-
const rows = await this.store.query.query({
|
|
56506
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56507
|
-
filter: {
|
|
56508
|
-
...deviceId !== void 0 ? { where: { deviceId } } : {},
|
|
56509
|
-
orderBy: {
|
|
56510
|
-
field: "createdAt",
|
|
56511
|
-
direction: "desc"
|
|
56512
|
-
},
|
|
56513
|
-
limit: 1e3
|
|
56514
|
-
}
|
|
56515
|
-
});
|
|
56516
|
-
const out = [];
|
|
56517
|
-
for (const r of rows) {
|
|
56518
|
-
const parsed = this.parse(r.id, r.data);
|
|
56519
|
-
if (parsed !== null) out.push(parsed);
|
|
56520
|
-
}
|
|
56521
|
-
return out;
|
|
56522
|
-
}
|
|
56523
|
-
async setReviewed(id, reviewed) {
|
|
56524
|
-
await this.store.update.mutate({
|
|
56525
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56526
|
-
id,
|
|
56527
|
-
data: { reviewed }
|
|
56528
|
-
});
|
|
56529
|
-
}
|
|
56530
|
-
/** Release AND unsettle in one row write: the unpins the release implies
|
|
56531
|
-
* are owed from this moment, whether or not the caller lives to make them. */
|
|
56532
|
-
async setReleased(id, atMs) {
|
|
56533
|
-
await this.store.update.mutate({
|
|
56534
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56535
|
-
id,
|
|
56536
|
-
data: {
|
|
56537
|
-
releasedAt: atMs,
|
|
56538
|
-
pinsSettled: false
|
|
56539
|
-
}
|
|
56540
|
-
});
|
|
56541
|
-
}
|
|
56542
|
-
async setPinsSettled(id, settled) {
|
|
56543
|
-
await this.store.update.mutate({
|
|
56544
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56545
|
-
id,
|
|
56546
|
-
data: { pinsSettled: settled }
|
|
56547
|
-
});
|
|
56548
|
-
}
|
|
56549
|
-
async remove(id) {
|
|
56550
|
-
await this.store.delete.mutate({
|
|
56551
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56552
|
-
key: id
|
|
56553
|
-
});
|
|
56554
|
-
}
|
|
56555
|
-
parse(id, data) {
|
|
56556
|
-
let trackIds = [];
|
|
56557
|
-
try {
|
|
56558
|
-
trackIds = JSON.parse(String(data["trackIds"] ?? "[]"));
|
|
56559
|
-
} catch {
|
|
56560
|
-
trackIds = null;
|
|
56561
|
-
}
|
|
56562
|
-
const ids = TrackIdsSchema.safeParse(trackIds);
|
|
56563
|
-
const parsed = TrackingTapeRowSchema.safeParse({
|
|
56564
|
-
id,
|
|
56565
|
-
...data,
|
|
56566
|
-
note: data["note"] ?? null,
|
|
56567
|
-
releasedAt: data["releasedAt"] ?? null,
|
|
56568
|
-
trackIds: ids.success ? ids.data : null
|
|
56569
|
-
});
|
|
56570
|
-
if (parsed.success) return parsed.data;
|
|
56571
|
-
this.logger.warn("tracking tape index: skipped a malformed row", {
|
|
56572
|
-
meta: { id },
|
|
56573
|
-
...typeof data["deviceId"] === "number" ? { tags: { deviceId: data["deviceId"] } } : {}
|
|
56574
|
-
});
|
|
56575
|
-
return null;
|
|
56576
|
-
}
|
|
56577
|
-
};
|
|
56578
|
-
//#endregion
|
|
56579
57080
|
//#region src/pipeline-analytics/tracking-bench/recorder/tape-status.ts
|
|
56580
57081
|
/** The one node that may hold tapes: sqlite and bulk state are hub-only, and
|
|
56581
57082
|
* an agent persists nothing (spec §3.1). */
|
|
@@ -96128,6 +96629,30 @@ async function encodeClipQuery(encode, text, modelId, warn) {
|
|
|
96128
96629
|
}
|
|
96129
96630
|
}
|
|
96130
96631
|
//#endregion
|
|
96632
|
+
//#region src/pipeline-analytics/nc-owner-media-reader.ts
|
|
96633
|
+
/** Keep only the rows written for THIS generation of the event id. */
|
|
96634
|
+
function ofGeneration(files, scope) {
|
|
96635
|
+
return files.filter((f) => f.timestamp === scope.recordAt);
|
|
96636
|
+
}
|
|
96637
|
+
function makeNcOwnerMediaReader(deps) {
|
|
96638
|
+
return async (ownerKind, ownerId, eventScope) => {
|
|
96639
|
+
if (ownerKind === "track") return deps.media.listByOwner("track", await deps.resolveTrackOwnerId(ownerId));
|
|
96640
|
+
if (eventScope === void 0) return deps.media.listByOwner("object", ownerId);
|
|
96641
|
+
const onCamera = await deps.media.listByOwner("object", ownerId, eventScope.deviceId);
|
|
96642
|
+
const kept = ofGeneration(onCamera, eventScope);
|
|
96643
|
+
if (kept.length < onCamera.length) deps.logger.warn("event media of another record on a reused event id — not used", {
|
|
96644
|
+
tags: { deviceId: eventScope.deviceId },
|
|
96645
|
+
meta: {
|
|
96646
|
+
ownerId,
|
|
96647
|
+
recordAt: eventScope.recordAt,
|
|
96648
|
+
refused: onCamera.length - kept.length,
|
|
96649
|
+
kept: kept.length
|
|
96650
|
+
}
|
|
96651
|
+
});
|
|
96652
|
+
return kept;
|
|
96653
|
+
};
|
|
96654
|
+
}
|
|
96655
|
+
//#endregion
|
|
96131
96656
|
//#region src/pipeline-analytics/store/settings-raw-mirror.ts
|
|
96132
96657
|
/** How long a mirrored entry is served before a refresh is started behind the
|
|
96133
96658
|
* next reader. See the module doc for why this matches the per-kind caches'
|
|
@@ -97482,6 +98007,12 @@ var TRACKER_FACT_LOG_SAMPLE_EVERY = 10;
|
|
|
97482
98007
|
* positive spawns (review M1: ~1 750 `new` decisions per camera per hour) —
|
|
97483
98008
|
* are sampled harder; the per-minute summary carries their exact counts. */
|
|
97484
98009
|
var TRACKER_PIECE_LOG_SAMPLE_EVERY = 50;
|
|
98010
|
+
/** Out-of-order frames (follow-up 617, F1: ~1 % of frames) log the first N,
|
|
98011
|
+
* then one in this many — every line carries the count; the per-minute
|
|
98012
|
+
* summary carries the exact `outOfOrderFrames`. */
|
|
98013
|
+
var TRACKER_OUT_OF_ORDER_LOG_SAMPLE_EVERY = 50;
|
|
98014
|
+
/** Clock-step WARNs past the first ten (fix round 3, MEDIUM-1). */
|
|
98015
|
+
var TRACKER_CLOCK_STEP_LOG_SAMPLE_EVERY = 50;
|
|
97485
98016
|
/** First N, then one in `every` — the refusal stream's cadence (D391). */
|
|
97486
98017
|
function sampledTrackerFact(count, every = TRACKER_FACT_LOG_SAMPLE_EVERY) {
|
|
97487
98018
|
return count <= TRACKER_FACT_LOG_ALWAYS_FIRST || count % every === 0;
|
|
@@ -100452,7 +100983,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
100452
100983
|
}
|
|
100453
100984
|
});
|
|
100454
100985
|
},
|
|
100455
|
-
getMediaForOwner:
|
|
100986
|
+
getMediaForOwner: makeNcOwnerMediaReader({
|
|
100987
|
+
media: stores.mediaStore,
|
|
100988
|
+
resolveTrackOwnerId: (trackId) => resolveMediaOwnerId("track", trackId, (id) => this.trackAliasStore?.resolve(id) ?? Promise.resolve(null), (failure) => this.aliasResolveFailureLogger?.(failure), "getMediaForOwner"),
|
|
100989
|
+
logger: this.ctx.logger.child("nc-media")
|
|
100990
|
+
}),
|
|
100456
100991
|
getSubjectTrail: async (deviceId, trackId) => {
|
|
100457
100992
|
const dims = this.lastFrameDimsByDevice.get(deviceId);
|
|
100458
100993
|
if (dims === void 0 || dims.w <= 0 || dims.h <= 0) return [];
|
|
@@ -102125,9 +102660,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
102125
102660
|
let splitCount = 0;
|
|
102126
102661
|
let ignoredCount = 0;
|
|
102127
102662
|
let newCount = 0;
|
|
102663
|
+
let outOfOrderCount = 0;
|
|
102664
|
+
let clockStepCount = 0;
|
|
102128
102665
|
const endedCounts = /* @__PURE__ */ new Map();
|
|
102129
102666
|
const logLink = (f) => {
|
|
102130
|
-
const
|
|
102667
|
+
const decision = {
|
|
102131
102668
|
source,
|
|
102132
102669
|
t: f.t,
|
|
102133
102670
|
kind: f.kind,
|
|
@@ -102136,7 +102673,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
102136
102673
|
cost: f.cost,
|
|
102137
102674
|
gapMs: f.gapMs,
|
|
102138
102675
|
reason: f.reason,
|
|
102139
|
-
recognition: f.recognition
|
|
102676
|
+
recognition: f.recognition
|
|
102677
|
+
};
|
|
102678
|
+
const meta = {
|
|
102679
|
+
...decision,
|
|
102140
102680
|
candidates: f.candidates
|
|
102141
102681
|
};
|
|
102142
102682
|
switch (f.kind) {
|
|
@@ -102174,7 +102714,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
102174
102714
|
meta
|
|
102175
102715
|
});
|
|
102176
102716
|
return;
|
|
102177
|
-
case "new":
|
|
102717
|
+
case "new": {
|
|
102718
|
+
if (f.reason === "clock-step") {
|
|
102719
|
+
trackerLog.debug("tracker: piece decided new — closed by a frame clock step", {
|
|
102720
|
+
tags: { deviceId },
|
|
102721
|
+
meta
|
|
102722
|
+
});
|
|
102723
|
+
return;
|
|
102724
|
+
}
|
|
102178
102725
|
if (f.reason === "unconfirmed") {
|
|
102179
102726
|
trackerLog.debug("tracker: piece decided new — never confirmed", {
|
|
102180
102727
|
tags: { deviceId },
|
|
@@ -102182,6 +102729,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
102182
102729
|
});
|
|
102183
102730
|
return;
|
|
102184
102731
|
}
|
|
102732
|
+
const rejectedPredecessors = confirmedPredecessorRejections(f.candidates);
|
|
102733
|
+
if (rejectedPredecessors.length > 0) {
|
|
102734
|
+
trackerLog.info("tracker: piece decided new — a confirmed predecessor was rejected", {
|
|
102735
|
+
tags: { deviceId },
|
|
102736
|
+
meta: {
|
|
102737
|
+
...decision,
|
|
102738
|
+
rejectedPredecessors
|
|
102739
|
+
}
|
|
102740
|
+
});
|
|
102741
|
+
return;
|
|
102742
|
+
}
|
|
102185
102743
|
newCount += 1;
|
|
102186
102744
|
if (!sampledTrackerFact(newCount, TRACKER_PIECE_LOG_SAMPLE_EVERY)) return;
|
|
102187
102745
|
trackerLog.info("tracker: piece decided new — no predecessor", {
|
|
@@ -102192,6 +102750,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
102192
102750
|
}
|
|
102193
102751
|
});
|
|
102194
102752
|
return;
|
|
102753
|
+
}
|
|
102195
102754
|
}
|
|
102196
102755
|
};
|
|
102197
102756
|
p.setTrackerObservers({
|
|
@@ -102212,6 +102771,39 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
102212
102771
|
});
|
|
102213
102772
|
},
|
|
102214
102773
|
link: logLink,
|
|
102774
|
+
clockStep: (f) => {
|
|
102775
|
+
clockStepCount += 1;
|
|
102776
|
+
if (!sampledTrackerFact(clockStepCount, TRACKER_CLOCK_STEP_LOG_SAMPLE_EVERY)) return;
|
|
102777
|
+
trackerLog.warn("tracker: frame clock stepped back — tracklets ended, clock rebased", {
|
|
102778
|
+
tags: { deviceId },
|
|
102779
|
+
meta: {
|
|
102780
|
+
source,
|
|
102781
|
+
t: f.t,
|
|
102782
|
+
lastFrameT: f.lastFrameT,
|
|
102783
|
+
behindMs: f.behindMs,
|
|
102784
|
+
ended: f.ended,
|
|
102785
|
+
clockStepCount,
|
|
102786
|
+
sampledEvery: TRACKER_CLOCK_STEP_LOG_SAMPLE_EVERY
|
|
102787
|
+
}
|
|
102788
|
+
});
|
|
102789
|
+
},
|
|
102790
|
+
outOfOrder: (f) => {
|
|
102791
|
+
outOfOrderCount += 1;
|
|
102792
|
+
if (!sampledTrackerFact(outOfOrderCount, TRACKER_OUT_OF_ORDER_LOG_SAMPLE_EVERY)) return;
|
|
102793
|
+
trackerLog.info("tracker: out-of-order frame dropped — older than the latest frame", {
|
|
102794
|
+
tags: { deviceId },
|
|
102795
|
+
meta: {
|
|
102796
|
+
source,
|
|
102797
|
+
t: f.t,
|
|
102798
|
+
lastFrameT: f.lastFrameT,
|
|
102799
|
+
behindMs: f.behindMs,
|
|
102800
|
+
detections: f.detections,
|
|
102801
|
+
pendingClockStep: f.pendingClockStep,
|
|
102802
|
+
outOfOrderCount,
|
|
102803
|
+
sampledEvery: TRACKER_OUT_OF_ORDER_LOG_SAMPLE_EVERY
|
|
102804
|
+
}
|
|
102805
|
+
});
|
|
102806
|
+
},
|
|
102215
102807
|
ended: (f) => {
|
|
102216
102808
|
const k = `${f.reason}:${String(f.confirmed)}`;
|
|
102217
102809
|
const n = (endedCounts.get(k) ?? 0) + 1;
|
|
@@ -102353,6 +102945,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
102353
102945
|
});
|
|
102354
102946
|
});
|
|
102355
102947
|
this.reportTrackerSummary(deviceId, key, processor, "frame");
|
|
102948
|
+
if (result.droppedOutOfOrder === true) return;
|
|
102356
102949
|
for (const r of processor.getLastLabelWriteRefusals()) {
|
|
102357
102950
|
const message = r.reason.kind === "macro-class" ? "label tier collapse refused" : "label write refused";
|
|
102358
102951
|
this.ctx.logger.info(message, {
|