@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, $n as ruleDeviceCompatCapQueries, $t as TimelapseRuleInputSchema, A as EMBEDDING_REQUIREMENTS_FAILED, An as isDetectionMacroClass, Ar as ZodObject, At as NcTemplateVarDescriptorSchema, B as ListTrackRelationsInputSchema, Bn as memoizePinnedFfmpeg, Br as record, Bt as SCENE_DEFAULT_ANCHOR_THRESHOLD, C as DEFAULT_EVENT_COLOR, Cn as encodeVectorBase64, Cr as customAction, Ct as NcSnoozeInputSchema, D as DeclaredDevices, Dn as gifDeriveSpeed, Dr as nodePin, Dt as NcTaxonomySchema, E as DETECTION_PIPELINE_CAP_NAME, En as failureContributionCapability, Er as isDeviceScopedCap, Et as NcSystemEventKindSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as isSystemDelivery, Fr as literal, Ft as RECORDING_SOURCE_CAMSTACK_ADDON, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as plateGalleryCapability, Gr as unknown, Gt as SCENE_DIVERGED, H as LockStateSchema, Hn as occupancyScope, Hr as string, Ht as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, I as FIRST_LEVEL_MACRO_CLASSES, In as isSystemEventSummaryRule, Ir as looseObject, It as RebuildObjectEmbeddingsRefusalSchema, J as MOTION_CLOSE_AFTER_MS, Jn as readTimelapseGeneratedAt, Jt as SetTrackPurityInputSchema, K as MAX_TRACK_PURITIES, Kn as readDeviceStateFrom, Kr as EventCategory, Kt as SCENE_RESET_RECAPTURES, L as FULL_IMAGE_BBOX, Ln as isVectorIndexDimConflict, Lr as number, Lt as RetrainStatusSchema, M as EVENT_DENSITY_BATCH_MAX, Mn as isKnownTemplateVar, Mr as array, Mt as OpsLogEntrySchema, N as EVENT_KIND_BY_CAP, Nn as isMediaOwnerType, Nr as boolean, O as DetectionSourceSchema, On as gifFromMp4Args, Ot as NcTemplatePreviewInputSchema, P as EVENT_OWNER_TYPES, Pn as isScheduleActive, Pr as discriminatedUnion, Pt as RECORDING_EXPORT_MAX_READ_BYTES, Q as NC_AUDIO_CONFIRM_HITS_DEFAULT, Qn as ruleBelongsToAlarm$1, Qt as TRACK_RELATIONS_DEFAULT_LIMIT, R as FailureCounters, Rn as kebabToCamel, Rr as object, Rt as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, S as CoverStateSchema, Sr as createEvent, St as NcScheduleSchema, T as DETECTION_MACRO_CLASSES, Tn as faceGalleryCapability, Tr as hydrateSchema, Tt as NcSnoozeSuppressedSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Ur as tuple, Ut as SCENE_DEFAULT_QUIET_SECONDS, V as ListTrackRelationsResultSchema, Vn as notificationRulesCapability, Vr as strictObject, Vt as SCENE_DEFAULT_CHECK_INTERVAL_SEC, W as MAX_BIRTH_DECISION_RECORDS, Wn as pipelineAnalyticsCapability, Wr as union, Wt as SCENE_DEFAULT_UNCOVERED_POLICY, X as MotionTriggeredBySchema, Xn as resolveLocationMode, Xt as SetTrackRelationInputSchema, Y as MediaFileKindEnum, Yn as resolveClipSearchMinScore, Yt as SetTrackPurityResultSchema, Z as NC_ALARM_SYSTEM_EVENT_KINDS, Zt as TIMELAPSE_DENSE_FLOOR_SEC, _ as BaseDevice, _n as describeSystemEventFamilyProblem, _r as isSourceCap, _t as NcRuleInputSchema, a as CLIP_EMBEDDING_STEP_ID, an as TrackSideSchema, ar as subKindsOf, at as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, b as ClearTrackRelationInputSchema, bn as embeddingRefusalOf, br as BaseAddon, bt as NcRuleTargetSchema, c as resolveClusterModelPin, cn as alarmPanelCapability, cr as systemEventFilterApplies, ct as NC_SNOOZE_MAX_MINUTES, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, dn as audioModeOf, dr as templateVarsFor, dt as NC_TAXONOMY, en as TimelapseRulePatchSchema, er as ruleKindOf, et as NC_CLIP_POST_ROLL_DEFAULT_SEC, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fn as buildEventKindDescriptor, fr as vectorDimFromBase64, ft as NC_TEMPLATE_VARS, g as BIRTH_DECISION_DEFAULT_LIMIT, gn as describeSystemEventDeliveryProblem, gr as evaluateSensorEdge, gt as NcConditionsSchema, h as ArchivedDebugNoteSchema, hn as deriveRecordingMode, hr as canonicalHash, ht as NcConditionDescriptorSchema, in as TrackRelationSchema, ir as storageOccupancyCapability, it as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, jn as isEventOwnerType, jr as _enum, jt as OPS_LOG_DEFAULT_LIMIT, k as EMBEDDING_MODEL_FAILED, kn as isAudioClassName, kr as sleep$1, kt as NcTemplatePreviewSchema, l as APPEARANCE_BIN_COUNT, ln as assertTimelapseCadences, lr as systemEventSummaryScheduleOf, lt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, m as AlarmArmModeSchema, mn as cosineSimilarity$1, mr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, mt as NcAlarmSettingsPatchSchema, nn as TrackPuritySchema, nr as sensorRaisedOf, nt as NC_CLIP_RETENTION_MAX_SEC, o as FACE_EMBEDDING_STEP_ID, on as TrackSourceSchema, or as systemEventDeliveryProblems, ot as NC_DEFAULT_SNOOZE_MINUTES, p as AUDIO_MACRO_LABELS, pn as consumableLowPercentOf, pr as zoneAnalyticsCapability, pt as NcAlarmConfigSchema, q as MAX_TRACK_RELATIONS, qn as readMotionTriggeredBy, qt as SceneMonitorSchema, rn as TrackRelationKindSchema, rr as servedTemplateVars, rt as NC_CONDITION_CATALOG, s as readClusterModelPinStrict, sn as addonWidgetsSourceCapability, sr as systemEventFamilyProblems, st as NC_SENSOR_TRANSITION_KINDS, t as BUILTIN_CLIP_MODELS, tn as TimelapseRuleSchema, tr as sceneMonitorCapability, tt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, u as APPEARANCE_BLOCKS, un as audioMetricsCapability, ur as templateCarriesSummary, ut as NC_TANK_SYSTEM_EVENT_KINDS, v as BirthDecisionRecordSchema, vr as DeviceType, vt as NcRulePatchSchema, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wn as evictionPolicyOfLocation, wr as defineCustomActions, wt as NcSnoozeSchema, x as ClearTrackRelationResultSchema, xr as CamProfileSchema, xt as NcRuleUpdateInputSchema, y as COCO_TO_MACRO, yr as errMsg, yt as NcRuleSchema, z as LabelAttributionSchema, zn as mayWriteToLocation, zr as partialRecord, zt as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS } from "../clip-model-registry-
|
|
1
|
+
import { $ as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, $n as ruleDeviceCompatCapQueries, $t as TimelapseRuleInputSchema, A as EMBEDDING_REQUIREMENTS_FAILED, An as isDetectionMacroClass, Ar as ZodObject, At as NcTemplateVarDescriptorSchema, B as ListTrackRelationsInputSchema, Bn as memoizePinnedFfmpeg, Br as record, Bt as SCENE_DEFAULT_ANCHOR_THRESHOLD, C as DEFAULT_EVENT_COLOR, Cn as encodeVectorBase64, Cr as customAction, Ct as NcSnoozeInputSchema, D as DeclaredDevices, Dn as gifDeriveSpeed, Dr as nodePin, Dt as NcTaxonomySchema, E as DETECTION_PIPELINE_CAP_NAME, En as failureContributionCapability, Er as isDeviceScopedCap, Et as NcSystemEventKindSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as isSystemDelivery, Fr as literal, Ft as RECORDING_SOURCE_CAMSTACK_ADDON, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as plateGalleryCapability, Gr as unknown, Gt as SCENE_DIVERGED, H as LockStateSchema, Hn as occupancyScope, Hr as string, Ht as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, I as FIRST_LEVEL_MACRO_CLASSES, In as isSystemEventSummaryRule, Ir as looseObject, It as RebuildObjectEmbeddingsRefusalSchema, J as MOTION_CLOSE_AFTER_MS, Jn as readTimelapseGeneratedAt, Jt as SetTrackPurityInputSchema, K as MAX_TRACK_PURITIES, Kn as readDeviceStateFrom, Kr as EventCategory, Kt as SCENE_RESET_RECAPTURES, L as FULL_IMAGE_BBOX, Ln as isVectorIndexDimConflict, Lr as number, Lt as RetrainStatusSchema, M as EVENT_DENSITY_BATCH_MAX, Mn as isKnownTemplateVar, Mr as array, Mt as OpsLogEntrySchema, N as EVENT_KIND_BY_CAP, Nn as isMediaOwnerType, Nr as boolean, O as DetectionSourceSchema, On as gifFromMp4Args, Ot as NcTemplatePreviewInputSchema, P as EVENT_OWNER_TYPES, Pn as isScheduleActive, Pr as discriminatedUnion, Pt as RECORDING_EXPORT_MAX_READ_BYTES, Q as NC_AUDIO_CONFIRM_HITS_DEFAULT, Qn as ruleBelongsToAlarm$1, Qt as TRACK_RELATIONS_DEFAULT_LIMIT, R as FailureCounters, Rn as kebabToCamel, Rr as object, Rt as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, S as CoverStateSchema, Sr as createEvent, St as NcScheduleSchema, T as DETECTION_MACRO_CLASSES, Tn as faceGalleryCapability, Tr as hydrateSchema, Tt as NcSnoozeSuppressedSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Ur as tuple, Ut as SCENE_DEFAULT_QUIET_SECONDS, V as ListTrackRelationsResultSchema, Vn as notificationRulesCapability, Vr as strictObject, Vt as SCENE_DEFAULT_CHECK_INTERVAL_SEC, W as MAX_BIRTH_DECISION_RECORDS, Wn as pipelineAnalyticsCapability, Wr as union, Wt as SCENE_DEFAULT_UNCOVERED_POLICY, X as MotionTriggeredBySchema, Xn as resolveLocationMode, Xt as SetTrackRelationInputSchema, Y as MediaFileKindEnum, Yn as resolveClipSearchMinScore, Yt as SetTrackPurityResultSchema, Z as NC_ALARM_SYSTEM_EVENT_KINDS, Zt as TIMELAPSE_DENSE_FLOOR_SEC, _ as BaseDevice, _n as describeSystemEventFamilyProblem, _r as isSourceCap, _t as NcRuleInputSchema, a as CLIP_EMBEDDING_STEP_ID, an as TrackSideSchema, ar as subKindsOf, at as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, b as ClearTrackRelationInputSchema, bn as embeddingRefusalOf, br as BaseAddon, bt as NcRuleTargetSchema, c as resolveClusterModelPin, cn as alarmPanelCapability, cr as systemEventFilterApplies, ct as NC_SNOOZE_MAX_MINUTES, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, dn as audioModeOf, dr as templateVarsFor, dt as NC_TAXONOMY, en as TimelapseRulePatchSchema, er as ruleKindOf, et as NC_CLIP_POST_ROLL_DEFAULT_SEC, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fn as buildEventKindDescriptor, fr as vectorDimFromBase64, ft as NC_TEMPLATE_VARS, g as BIRTH_DECISION_DEFAULT_LIMIT, gn as describeSystemEventDeliveryProblem, gr as evaluateSensorEdge, gt as NcConditionsSchema, h as ArchivedDebugNoteSchema, hn as deriveRecordingMode, hr as canonicalHash, ht as NcConditionDescriptorSchema, in as TrackRelationSchema, ir as storageOccupancyCapability, it as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, jn as isEventOwnerType, jr as _enum, jt as OPS_LOG_DEFAULT_LIMIT, k as EMBEDDING_MODEL_FAILED, kn as isAudioClassName, kr as sleep$1, kt as NcTemplatePreviewSchema, l as APPEARANCE_BIN_COUNT, ln as assertTimelapseCadences, lr as systemEventSummaryScheduleOf, lt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, m as AlarmArmModeSchema, mn as cosineSimilarity$1, mr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, mt as NcAlarmSettingsPatchSchema, nn as TrackPuritySchema, nr as sensorRaisedOf, nt as NC_CLIP_RETENTION_MAX_SEC, o as FACE_EMBEDDING_STEP_ID, on as TrackSourceSchema, or as systemEventDeliveryProblems, ot as NC_DEFAULT_SNOOZE_MINUTES, p as AUDIO_MACRO_LABELS, pn as consumableLowPercentOf, pr as zoneAnalyticsCapability, pt as NcAlarmConfigSchema, q as MAX_TRACK_RELATIONS, qn as readMotionTriggeredBy, qt as SceneMonitorSchema, rn as TrackRelationKindSchema, rr as servedTemplateVars, rt as NC_CONDITION_CATALOG, s as readClusterModelPinStrict, sn as addonWidgetsSourceCapability, sr as systemEventFamilyProblems, st as NC_SENSOR_TRANSITION_KINDS, t as BUILTIN_CLIP_MODELS, tn as TimelapseRuleSchema, tr as sceneMonitorCapability, tt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, u as APPEARANCE_BLOCKS, un as audioMetricsCapability, ur as templateCarriesSummary, ut as NC_TANK_SYSTEM_EVENT_KINDS, v as BirthDecisionRecordSchema, vr as DeviceType, vt as NcRulePatchSchema, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wn as evictionPolicyOfLocation, wr as defineCustomActions, wt as NcSnoozeSchema, x as ClearTrackRelationResultSchema, xr as CamProfileSchema, xt as NcRuleUpdateInputSchema, y as COCO_TO_MACRO, yr as errMsg, yt as NcRuleSchema, z as LabelAttributionSchema, zn as mayWriteToLocation, zr as partialRecord, zt as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS } from "../clip-model-registry-FGOIz3PZ.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";
|
|
@@ -20048,6 +20048,114 @@ function ncGroupSubjectText(texts, input) {
|
|
|
20048
20048
|
return texts.join([...names, ...others]);
|
|
20049
20049
|
}
|
|
20050
20050
|
//#endregion
|
|
20051
|
+
//#region src/notification-center/outbox-key.ts
|
|
20052
|
+
/**
|
|
20053
|
+
* The record kinds whose `recordId` is a per-table SQLite rowid (D474), and so
|
|
20054
|
+
* may be reused after a delete. `device-event` is NOT one: sensor events are
|
|
20055
|
+
* TEXT-keyed with a producer-minted id (`sensor-event-store.ts`).
|
|
20056
|
+
*/
|
|
20057
|
+
var ROWID_RECORD_KINDS = new Set([
|
|
20058
|
+
"object-event",
|
|
20059
|
+
"package-event",
|
|
20060
|
+
"audio-event"
|
|
20061
|
+
]);
|
|
20062
|
+
/** Is this kind's record id a reusable rowid? See {@link ROWID_RECORD_KINDS}. */
|
|
20063
|
+
function isRowidRecordKind(kind) {
|
|
20064
|
+
return ROWID_RECORD_KINDS.has(kind);
|
|
20065
|
+
}
|
|
20066
|
+
/** The record identity an outbox key input speaks for. */
|
|
20067
|
+
function outboxRecordIdentity(input) {
|
|
20068
|
+
return {
|
|
20069
|
+
recordId: input.recordId,
|
|
20070
|
+
deviceId: input.deviceId,
|
|
20071
|
+
recordAt: input.payload.subject.timestamp
|
|
20072
|
+
};
|
|
20073
|
+
}
|
|
20074
|
+
/**
|
|
20075
|
+
* The record GENERATION ref — `<recordId>@<deviceId>:<recordAt>`. Exported for
|
|
20076
|
+
* the specs and for a caller that must name a rowid record outside the outbox
|
|
20077
|
+
* (none today); a key is always built through {@link outboxEntryId}.
|
|
20078
|
+
*/
|
|
20079
|
+
function recordGenerationRef(record) {
|
|
20080
|
+
return `${record.recordId}@${String(record.deviceId)}:${String(record.recordAt)}`;
|
|
20081
|
+
}
|
|
20082
|
+
/**
|
|
20083
|
+
* The middle segment of the dedup id:
|
|
20084
|
+
*
|
|
20085
|
+
* 1. an explicit `dedupRef` (upgrade / burst revision);
|
|
20086
|
+
* 2. the track id — `maxPerTrack: 1` (a track id is random, never reused);
|
|
20087
|
+
* 3. for a rowid record, its generation ref;
|
|
20088
|
+
* 4. the record id as is (synthetic ids already carry device + instant).
|
|
20089
|
+
*/
|
|
20090
|
+
function outboxDedupRef(input) {
|
|
20091
|
+
if (input.dedupRef !== void 0) return input.dedupRef;
|
|
20092
|
+
if (input.trackId !== void 0) return input.trackId;
|
|
20093
|
+
if (isRowidRecordKind(input.recordKind)) return recordGenerationRef(outboxRecordIdentity(input));
|
|
20094
|
+
return input.recordId;
|
|
20095
|
+
}
|
|
20096
|
+
/** The outbox row id — the durable per-track / per-record fire guarantee. */
|
|
20097
|
+
function outboxEntryId(input) {
|
|
20098
|
+
return `${input.ruleId}:${outboxDedupRef(input)}:${input.targetId}`;
|
|
20099
|
+
}
|
|
20100
|
+
/**
|
|
20101
|
+
* The id this input had BEFORE D694 — but only when it differs from today's,
|
|
20102
|
+
* i.e. for a rowid record keyed on its record id. `null` otherwise: there is no
|
|
20103
|
+
* older spelling to look for.
|
|
20104
|
+
*/
|
|
20105
|
+
function legacyOutboxEntryId(input) {
|
|
20106
|
+
if (input.dedupRef !== void 0 || input.trackId !== void 0) return null;
|
|
20107
|
+
if (!isRowidRecordKind(input.recordKind)) return null;
|
|
20108
|
+
return `${input.ruleId}:${input.recordId}:${input.targetId}`;
|
|
20109
|
+
}
|
|
20110
|
+
/**
|
|
20111
|
+
* A STORED row's id as it would be built today, when the row was written under
|
|
20112
|
+
* the pre-D694 spelling; `null` when the stored id is already current (or was
|
|
20113
|
+
* built from an explicit dedup ref, which a row does not record and which never
|
|
20114
|
+
* involved the record id).
|
|
20115
|
+
*/
|
|
20116
|
+
function translatedLegacyEntryId(storedId, row) {
|
|
20117
|
+
const legacy = legacyOutboxEntryId(row);
|
|
20118
|
+
if (legacy === null || legacy !== storedId) return null;
|
|
20119
|
+
return outboxEntryId(row);
|
|
20120
|
+
}
|
|
20121
|
+
/**
|
|
20122
|
+
* Does a STORED row's payload really carry its record timestamp?
|
|
20123
|
+
*
|
|
20124
|
+
* The type says it always does, but a stored payload is checked only as an
|
|
20125
|
+
* object on the way back from the store (`rowToEntry`), so a malformed or very
|
|
20126
|
+
* old row can lack `subject`. Every read of {@link outboxRecordIdentity} on a
|
|
20127
|
+
* STORED row is preceded by this check. Takes the payload as `unknown` on
|
|
20128
|
+
* purpose: the static type cannot answer the question.
|
|
20129
|
+
*/
|
|
20130
|
+
function hasRecordTimestamp(payload) {
|
|
20131
|
+
if (typeof payload !== "object" || payload === null || !("subject" in payload)) return false;
|
|
20132
|
+
const subject = payload.subject;
|
|
20133
|
+
return typeof subject === "object" && subject !== null && "timestamp" in subject && typeof subject.timestamp === "number";
|
|
20134
|
+
}
|
|
20135
|
+
/** Do two rows speak for the same RECORD? Id, camera and record timestamp. */
|
|
20136
|
+
function isSameOutboxRecord(a, b) {
|
|
20137
|
+
return a.recordId === b.recordId && a.deviceId === b.deviceId && a.recordAt === b.recordAt;
|
|
20138
|
+
}
|
|
20139
|
+
/**
|
|
20140
|
+
* The scope of the event a payload's `eventId` names.
|
|
20141
|
+
*
|
|
20142
|
+
* Usually the row's own subject. A COMBINED alarm row is the exception: its
|
|
20143
|
+
* subject is the alarm transition (its own instant), while its `eventId` is the
|
|
20144
|
+
* triggering rule's event — so the generation is the alarm SOURCE's camera and
|
|
20145
|
+
* timestamp (`NcRuleFireRef`), never the transition's.
|
|
20146
|
+
*/
|
|
20147
|
+
function eventMediaScopeOf(subject) {
|
|
20148
|
+
const source = subject.systemEvent?.alarmSource;
|
|
20149
|
+
if (source !== void 0) return {
|
|
20150
|
+
deviceId: source.deviceId,
|
|
20151
|
+
recordAt: source.timestamp
|
|
20152
|
+
};
|
|
20153
|
+
return {
|
|
20154
|
+
deviceId: subject.deviceId,
|
|
20155
|
+
recordAt: subject.timestamp
|
|
20156
|
+
};
|
|
20157
|
+
}
|
|
20158
|
+
//#endregion
|
|
20051
20159
|
//#region src/notification-center/footage-source.ts
|
|
20052
20160
|
/**
|
|
20053
20161
|
* Decide where this window must be cut from. Pure and total.
|
|
@@ -22013,7 +22121,7 @@ var NcDispatcher = class {
|
|
|
22013
22121
|
});
|
|
22014
22122
|
if (policy === "keyFrame" || frame === "full" || frame === "boxed") owners.reverse();
|
|
22015
22123
|
for (const owner of owners) try {
|
|
22016
|
-
const files = await this.deps.getMediaForOwner(owner.kind, owner.id);
|
|
22124
|
+
const files = owner.kind === "object" ? await this.deps.getMediaForOwner(owner.kind, owner.id, eventMediaScopeOf(subject)) : await this.deps.getMediaForOwner(owner.kind, owner.id);
|
|
22017
22125
|
if (files.length === 0) continue;
|
|
22018
22126
|
const preference = frame !== void 0 ? framePreference(frame, owner.kind) : policy === "best-matching" ? bestMatchingKindPreference(signal, owner.kind) : attachmentKindPreference(policy, owner.kind);
|
|
22019
22127
|
const ordered = [];
|
|
@@ -24298,11 +24406,6 @@ var DEFAULT_MAX_ATTEMPTS = 8;
|
|
|
24298
24406
|
var DEFAULT_DRAIN_BATCH = 20;
|
|
24299
24407
|
/** Dedup ids remembered at boot — bounded to recent rows (dedup horizon). */
|
|
24300
24408
|
var DEDUP_HYDRATE_WINDOW_MS = 168 * 36e5;
|
|
24301
|
-
/** Stable dedup id — the durable per-track/per-record fire guarantee. */
|
|
24302
|
-
function outboxEntryId(input) {
|
|
24303
|
-
const dedupRef = input.dedupRef ?? input.trackId ?? input.recordId;
|
|
24304
|
-
return `${input.ruleId}:${dedupRef}:${input.targetId}`;
|
|
24305
|
-
}
|
|
24306
24409
|
var NcOutbox = class {
|
|
24307
24410
|
pending = /* @__PURE__ */ new Map();
|
|
24308
24411
|
/** Every id ever enqueued within the dedup horizon (incl. sent/dead). */
|
|
@@ -24379,20 +24482,27 @@ var NcOutbox = class {
|
|
|
24379
24482
|
}
|
|
24380
24483
|
});
|
|
24381
24484
|
let superseded = 0;
|
|
24485
|
+
let translated = 0;
|
|
24382
24486
|
for (const row of rows) {
|
|
24383
|
-
|
|
24487
|
+
const entry = rowToEntry$1(row.id, row.data);
|
|
24488
|
+
const current = entry === null ? null : this.translateLegacy(row.id, entry);
|
|
24489
|
+
if (this.knownIds.has(row.id) || current !== null && this.knownIds.has(current)) {
|
|
24384
24490
|
superseded += 1;
|
|
24385
24491
|
continue;
|
|
24386
24492
|
}
|
|
24387
24493
|
this.knownIds.add(row.id);
|
|
24388
|
-
|
|
24494
|
+
if (current !== null) {
|
|
24495
|
+
this.knownIds.add(current);
|
|
24496
|
+
translated += 1;
|
|
24497
|
+
}
|
|
24389
24498
|
if (entry !== null && entry.status === "pending") this.pending.set(entry.id, entry);
|
|
24390
24499
|
}
|
|
24391
24500
|
this.horizonLoaded = true;
|
|
24392
24501
|
this.logger.info("notification outbox loaded", { meta: {
|
|
24393
24502
|
pending: this.pending.size,
|
|
24394
24503
|
known: this.knownIds.size,
|
|
24395
|
-
superseded
|
|
24504
|
+
superseded,
|
|
24505
|
+
translated
|
|
24396
24506
|
} });
|
|
24397
24507
|
} catch (err) {
|
|
24398
24508
|
this.logger.warn("notification outbox load failed", { meta: { error: String(err) } });
|
|
@@ -24416,7 +24526,7 @@ var NcOutbox = class {
|
|
|
24416
24526
|
for (const input of inputs) {
|
|
24417
24527
|
const id = outboxEntryId(input);
|
|
24418
24528
|
if (this.knownIds.has(id)) continue;
|
|
24419
|
-
if (!this.horizonLoaded && await this.
|
|
24529
|
+
if (!this.horizonLoaded && await this.alreadyStored(id, input)) {
|
|
24420
24530
|
this.knownIds.add(id);
|
|
24421
24531
|
this.logger.info("outbox enqueue deduped against the store — the horizon is still cold", {
|
|
24422
24532
|
tags: { deviceId: input.deviceId },
|
|
@@ -24633,7 +24743,7 @@ var NcOutbox = class {
|
|
|
24633
24743
|
*/
|
|
24634
24744
|
async supersede(input) {
|
|
24635
24745
|
const wanted = new Set(input.targetIds);
|
|
24636
|
-
const victims = [...this.pending.values()].filter((e) => e.ruleId === input.ruleId && e.
|
|
24746
|
+
const victims = [...this.pending.values()].filter((e) => e.ruleId === input.ruleId && wanted.has(e.targetId) && hasRecordTimestamp(e.payload) && isSameOutboxRecord(outboxRecordIdentity(e), input));
|
|
24637
24747
|
let dropped = 0;
|
|
24638
24748
|
for (const entry of victims) try {
|
|
24639
24749
|
await this.store.delete.mutate({
|
|
@@ -24749,6 +24859,81 @@ var NcOutbox = class {
|
|
|
24749
24859
|
await this.mutate(retry);
|
|
24750
24860
|
}
|
|
24751
24861
|
/**
|
|
24862
|
+
* Cold-horizon dedup: is this intent already a row in the store?
|
|
24863
|
+
*
|
|
24864
|
+
* Today's id first. Then, for a rowid record, the pre-D694 spelling — which
|
|
24865
|
+
* is accepted ONLY when the row found speaks for the SAME record (camera and
|
|
24866
|
+
* record timestamp too). A legacy row of a different record that reused the
|
|
24867
|
+
* rowid is exactly the collision D694 removes, and must not swallow this one.
|
|
24868
|
+
*/
|
|
24869
|
+
async alreadyStored(id, input) {
|
|
24870
|
+
if (await this.rowExists(id)) return true;
|
|
24871
|
+
const legacy = legacyOutboxEntryId(input);
|
|
24872
|
+
if (legacy === null) return false;
|
|
24873
|
+
const row = await this.readRow(legacy);
|
|
24874
|
+
if (row === null) return false;
|
|
24875
|
+
if (!hasRecordTimestamp(row.payload)) {
|
|
24876
|
+
this.logger.warn("outbox: a pre-D694 row carries no record timestamp — not used for dedup", {
|
|
24877
|
+
tags: { deviceId: row.deviceId },
|
|
24878
|
+
meta: {
|
|
24879
|
+
id,
|
|
24880
|
+
legacyId: legacy,
|
|
24881
|
+
ruleId: input.ruleId
|
|
24882
|
+
}
|
|
24883
|
+
});
|
|
24884
|
+
return false;
|
|
24885
|
+
}
|
|
24886
|
+
if (isSameOutboxRecord(outboxRecordIdentity(row), outboxRecordIdentity(input))) return true;
|
|
24887
|
+
this.logger.info("outbox: a pre-D694 row shares this rowid but is a different record — not a duplicate", {
|
|
24888
|
+
tags: { deviceId: input.deviceId },
|
|
24889
|
+
meta: {
|
|
24890
|
+
id,
|
|
24891
|
+
legacyId: legacy,
|
|
24892
|
+
legacyDeviceId: row.deviceId,
|
|
24893
|
+
ruleId: input.ruleId
|
|
24894
|
+
}
|
|
24895
|
+
});
|
|
24896
|
+
return false;
|
|
24897
|
+
}
|
|
24898
|
+
/**
|
|
24899
|
+
* The id a stored pre-D694 row would have today, or `null`.
|
|
24900
|
+
*
|
|
24901
|
+
* Guarded per row: a stored payload is validated only as an object
|
|
24902
|
+
* (`rowToEntry`), and a row with no `subject.timestamp` would otherwise throw
|
|
24903
|
+
* inside {@link load} and leave the WHOLE dedup horizon cold. Such a row is
|
|
24904
|
+
* still loaded under its stored id; it just gets no translated twin.
|
|
24905
|
+
*/
|
|
24906
|
+
translateLegacy(storedId, entry) {
|
|
24907
|
+
if (legacyOutboxEntryId(entry) !== storedId) return null;
|
|
24908
|
+
if (!hasRecordTimestamp(entry.payload)) {
|
|
24909
|
+
this.logger.warn("outbox: a pre-D694 row carries no record timestamp — loaded without translation", {
|
|
24910
|
+
tags: { deviceId: entry.deviceId },
|
|
24911
|
+
meta: {
|
|
24912
|
+
id: storedId,
|
|
24913
|
+
ruleId: entry.ruleId
|
|
24914
|
+
}
|
|
24915
|
+
});
|
|
24916
|
+
return null;
|
|
24917
|
+
}
|
|
24918
|
+
return translatedLegacyEntryId(storedId, entry);
|
|
24919
|
+
}
|
|
24920
|
+
/** One stored row by id, or `null` (absent, unreadable, or a failed read). */
|
|
24921
|
+
async readRow(id) {
|
|
24922
|
+
try {
|
|
24923
|
+
const row = await this.store.get.query({
|
|
24924
|
+
collection: NC_OUTBOX_COLLECTION,
|
|
24925
|
+
key: id
|
|
24926
|
+
});
|
|
24927
|
+
return isStoredRow(row) ? rowToEntry$1(id, row) : null;
|
|
24928
|
+
} catch (err) {
|
|
24929
|
+
this.logger.debug("outbox cold-horizon dedup read failed", { meta: {
|
|
24930
|
+
id,
|
|
24931
|
+
error: String(err)
|
|
24932
|
+
} });
|
|
24933
|
+
return null;
|
|
24934
|
+
}
|
|
24935
|
+
}
|
|
24936
|
+
/**
|
|
24752
24937
|
* Does a row already exist under this dedup id?
|
|
24753
24938
|
*
|
|
24754
24939
|
* Asked ONLY while {@link horizonLoaded} is false. {@link persist} writes
|
|
@@ -24842,6 +25027,10 @@ var OUTBOX_RECORD_KINDS = new Set([
|
|
|
24842
25027
|
function isOutboxRecordKind(x) {
|
|
24843
25028
|
return typeof x === "string" && OUTBOX_RECORD_KINDS.has(x);
|
|
24844
25029
|
}
|
|
25030
|
+
/** `settingsStore.get` answers `unknown` — a guard, never a cast. */
|
|
25031
|
+
function isStoredRow(x) {
|
|
25032
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
25033
|
+
}
|
|
24845
25034
|
/** A stored row back to an entry — a guard, never a cast. Exported for the
|
|
24846
25035
|
* specs that read the store a centre wrote to. */
|
|
24847
25036
|
function rowToEntry$1(id, data) {
|
|
@@ -33208,7 +33397,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
33208
33397
|
...deps.dispatcher,
|
|
33209
33398
|
logger: this.logger.child("dispatch"),
|
|
33210
33399
|
texts: this.texts,
|
|
33211
|
-
getMediaForOwner: async (ownerKind, ownerId) => {
|
|
33400
|
+
getMediaForOwner: async (ownerKind, ownerId, eventScope) => {
|
|
33212
33401
|
const held = isSyntheticId(ownerId) ? this.syntheticMedia.get(ownerId) : void 0;
|
|
33213
33402
|
if (held !== void 0) return held.files;
|
|
33214
33403
|
const shelved = isStillShelfId(ownerId) ? this.stills?.get(ownerId) : void 0;
|
|
@@ -33217,7 +33406,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
33217
33406
|
if (linked !== void 0) return linked;
|
|
33218
33407
|
const departed = ownerKind === "track" && this.departureFallbacks.has(ownerId);
|
|
33219
33408
|
const owner = departed ? await this.resolveDepartedTrack(ownerId) : ownerId;
|
|
33220
|
-
const files = await deps.dispatcher.getMediaForOwner(ownerKind, owner);
|
|
33409
|
+
const files = await deps.dispatcher.getMediaForOwner(ownerKind, owner, eventScope);
|
|
33221
33410
|
if (files.length > 0 || !departed) return files;
|
|
33222
33411
|
return this.departureFallbackMedia(ownerId) ?? files;
|
|
33223
33412
|
},
|
|
@@ -35364,6 +35553,8 @@ var NotificationCenter = class NotificationCenter {
|
|
|
35364
35553
|
const dropped = await this.outbox.supersede({
|
|
35365
35554
|
ruleId: source.ruleId,
|
|
35366
35555
|
recordId: source.recordId,
|
|
35556
|
+
deviceId: source.deviceId,
|
|
35557
|
+
recordAt: source.timestamp,
|
|
35367
35558
|
targetIds: [...combined]
|
|
35368
35559
|
});
|
|
35369
35560
|
this.logger.info("alarm notification combined with the rule that triggered it", {
|
|
@@ -42387,6 +42578,280 @@ function parseDebugDeviceIds(url) {
|
|
|
42387
42578
|
return out;
|
|
42388
42579
|
}
|
|
42389
42580
|
//#endregion
|
|
42581
|
+
//#region src/pipeline-analytics/store/tracking-tape-index-store.ts
|
|
42582
|
+
/**
|
|
42583
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
42584
|
+
* write="one row per PROMOTED tracking-tape window (`debug.tape.promote`);
|
|
42585
|
+
* updated when the operator marks it reviewed or releases its pins;
|
|
42586
|
+
* deleted together with its file by `debug.tape.delete`."
|
|
42587
|
+
* retention="NOT an age clock and NOT track retention — a promoted window is
|
|
42588
|
+
* history being labelled (D681). Bounded by the promotion budget
|
|
42589
|
+
* (MAX_PROMOTED_TAPES = 60, 1 GiB): a promotion beyond it is REFUSED,
|
|
42590
|
+
* never satisfied by evicting another row."
|
|
42591
|
+
*/
|
|
42592
|
+
var TRACKING_TAPES_COLLECTION = "pipeline-analytics:tracking-tapes";
|
|
42593
|
+
var TRACKING_TAPES_COLUMNS = [
|
|
42594
|
+
{
|
|
42595
|
+
name: "id",
|
|
42596
|
+
type: "TEXT",
|
|
42597
|
+
primaryKey: true,
|
|
42598
|
+
notNull: true
|
|
42599
|
+
},
|
|
42600
|
+
{
|
|
42601
|
+
name: "deviceId",
|
|
42602
|
+
type: "INTEGER",
|
|
42603
|
+
notNull: true
|
|
42604
|
+
},
|
|
42605
|
+
{
|
|
42606
|
+
name: "scenario",
|
|
42607
|
+
type: "TEXT",
|
|
42608
|
+
notNull: true
|
|
42609
|
+
},
|
|
42610
|
+
{
|
|
42611
|
+
name: "note",
|
|
42612
|
+
type: "TEXT"
|
|
42613
|
+
},
|
|
42614
|
+
{
|
|
42615
|
+
name: "lighting",
|
|
42616
|
+
type: "TEXT",
|
|
42617
|
+
notNull: true
|
|
42618
|
+
},
|
|
42619
|
+
{
|
|
42620
|
+
name: "fromMs",
|
|
42621
|
+
type: "INTEGER",
|
|
42622
|
+
notNull: true
|
|
42623
|
+
},
|
|
42624
|
+
{
|
|
42625
|
+
name: "toMs",
|
|
42626
|
+
type: "INTEGER",
|
|
42627
|
+
notNull: true
|
|
42628
|
+
},
|
|
42629
|
+
{
|
|
42630
|
+
name: "state",
|
|
42631
|
+
type: "TEXT",
|
|
42632
|
+
notNull: true
|
|
42633
|
+
},
|
|
42634
|
+
{
|
|
42635
|
+
name: "frames",
|
|
42636
|
+
type: "INTEGER",
|
|
42637
|
+
notNull: true
|
|
42638
|
+
},
|
|
42639
|
+
{
|
|
42640
|
+
name: "skips",
|
|
42641
|
+
type: "INTEGER",
|
|
42642
|
+
notNull: true
|
|
42643
|
+
},
|
|
42644
|
+
{
|
|
42645
|
+
name: "gaps",
|
|
42646
|
+
type: "INTEGER",
|
|
42647
|
+
notNull: true
|
|
42648
|
+
},
|
|
42649
|
+
{
|
|
42650
|
+
name: "bytes",
|
|
42651
|
+
type: "INTEGER",
|
|
42652
|
+
notNull: true
|
|
42653
|
+
},
|
|
42654
|
+
{
|
|
42655
|
+
name: "reviewed",
|
|
42656
|
+
type: "BOOLEAN",
|
|
42657
|
+
notNull: true
|
|
42658
|
+
},
|
|
42659
|
+
{
|
|
42660
|
+
name: "trackIds",
|
|
42661
|
+
type: "TEXT",
|
|
42662
|
+
notNull: true
|
|
42663
|
+
},
|
|
42664
|
+
{
|
|
42665
|
+
name: "createdAt",
|
|
42666
|
+
type: "INTEGER",
|
|
42667
|
+
notNull: true
|
|
42668
|
+
},
|
|
42669
|
+
{
|
|
42670
|
+
name: "releasedAt",
|
|
42671
|
+
type: "INTEGER"
|
|
42672
|
+
},
|
|
42673
|
+
{
|
|
42674
|
+
name: "pinsSettled",
|
|
42675
|
+
type: "BOOLEAN",
|
|
42676
|
+
notNull: true
|
|
42677
|
+
}
|
|
42678
|
+
];
|
|
42679
|
+
var TrackingTapeRowSchema = object({
|
|
42680
|
+
id: string(),
|
|
42681
|
+
deviceId: number().int(),
|
|
42682
|
+
scenario: string(),
|
|
42683
|
+
note: string().nullable(),
|
|
42684
|
+
lighting: TapeLightingSchema,
|
|
42685
|
+
fromMs: number().int(),
|
|
42686
|
+
toMs: number().int(),
|
|
42687
|
+
state: _enum(["complete", "incomplete"]),
|
|
42688
|
+
frames: number().int(),
|
|
42689
|
+
skips: number().int(),
|
|
42690
|
+
gaps: number().int(),
|
|
42691
|
+
bytes: number().int(),
|
|
42692
|
+
reviewed: boolean(),
|
|
42693
|
+
trackIds: array(string()),
|
|
42694
|
+
createdAt: number().int(),
|
|
42695
|
+
releasedAt: number().int().nullable(),
|
|
42696
|
+
pinsSettled: boolean()
|
|
42697
|
+
});
|
|
42698
|
+
var TrackIdsSchema = array(string());
|
|
42699
|
+
/**
|
|
42700
|
+
* True when the SQLite settings backend would hand `text` back DECODED: it
|
|
42701
|
+
* parses any TEXT that starts with `[` or `{` and is valid JSON
|
|
42702
|
+
* (`decodeColumnValue` decodes by content, not by declared type). Free text
|
|
42703
|
+
* like that cannot round-trip through a TEXT column (follow-up 617, review M2).
|
|
42704
|
+
*/
|
|
42705
|
+
function decodesAsJson(text) {
|
|
42706
|
+
if (!(text.startsWith("[") || text.startsWith("{"))) return false;
|
|
42707
|
+
try {
|
|
42708
|
+
JSON.parse(text);
|
|
42709
|
+
return true;
|
|
42710
|
+
} catch {
|
|
42711
|
+
return false;
|
|
42712
|
+
}
|
|
42713
|
+
}
|
|
42714
|
+
/**
|
|
42715
|
+
* Free text (`scenario`, `note`) as the column hands it back: a value the
|
|
42716
|
+
* backend decoded is re-encoded, so a row written before `debug.tape.promote`
|
|
42717
|
+
* refused such text never vanishes (its whitespace may differ). Anything else
|
|
42718
|
+
* goes to the schema as it came.
|
|
42719
|
+
*/
|
|
42720
|
+
function freeText(raw) {
|
|
42721
|
+
return raw !== null && typeof raw === "object" ? JSON.stringify(raw) : raw;
|
|
42722
|
+
}
|
|
42723
|
+
/**
|
|
42724
|
+
* `trackIds` as the column hands it back. The SQLite backend decodes a TEXT
|
|
42725
|
+
* that starts with `[` on read (`decodeColumnValue`), so the store sees the
|
|
42726
|
+
* ARRAY it wrote; a string is parsed here only for a backend (or an old row)
|
|
42727
|
+
* that hands the text back as written. `JSON.parse(String(array))` threw on the
|
|
42728
|
+
* decoded array and every promoted row was skipped as malformed (follow-up 617).
|
|
42729
|
+
* `null` = unreadable, absent included (the column is NOT NULL, so absence is
|
|
42730
|
+
* corruption) — the row is reported, never repaired.
|
|
42731
|
+
*/
|
|
42732
|
+
function decodeTrackIds(raw) {
|
|
42733
|
+
if (raw === void 0 || raw === null) return null;
|
|
42734
|
+
if (typeof raw !== "string") return raw;
|
|
42735
|
+
try {
|
|
42736
|
+
return JSON.parse(raw);
|
|
42737
|
+
} catch {
|
|
42738
|
+
return null;
|
|
42739
|
+
}
|
|
42740
|
+
}
|
|
42741
|
+
var TrackingTapeIndexStore = class {
|
|
42742
|
+
store;
|
|
42743
|
+
logger;
|
|
42744
|
+
constructor(deps) {
|
|
42745
|
+
this.store = deps.store;
|
|
42746
|
+
this.logger = deps.logger;
|
|
42747
|
+
}
|
|
42748
|
+
static async declare(store) {
|
|
42749
|
+
await store.declareCollection.mutate({
|
|
42750
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42751
|
+
columns: [...TRACKING_TAPES_COLUMNS],
|
|
42752
|
+
indexes: [{
|
|
42753
|
+
name: "idx_trackingtapes_device_created",
|
|
42754
|
+
columns: ["deviceId", "createdAt"]
|
|
42755
|
+
}]
|
|
42756
|
+
});
|
|
42757
|
+
}
|
|
42758
|
+
async insert(row) {
|
|
42759
|
+
const { id, trackIds, ...rest } = TrackingTapeRowSchema.parse(row);
|
|
42760
|
+
await this.store.insert.mutate({
|
|
42761
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42762
|
+
record: {
|
|
42763
|
+
id,
|
|
42764
|
+
data: {
|
|
42765
|
+
...rest,
|
|
42766
|
+
trackIds: JSON.stringify(trackIds)
|
|
42767
|
+
}
|
|
42768
|
+
}
|
|
42769
|
+
});
|
|
42770
|
+
}
|
|
42771
|
+
async get(id) {
|
|
42772
|
+
const first = (await this.store.query.query({
|
|
42773
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42774
|
+
filter: {
|
|
42775
|
+
where: { id },
|
|
42776
|
+
limit: 1
|
|
42777
|
+
}
|
|
42778
|
+
}))[0];
|
|
42779
|
+
return first === void 0 ? null : this.parse(first.id, first.data);
|
|
42780
|
+
}
|
|
42781
|
+
async list(deviceId) {
|
|
42782
|
+
const rows = await this.store.query.query({
|
|
42783
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42784
|
+
filter: {
|
|
42785
|
+
...deviceId !== void 0 ? { where: { deviceId } } : {},
|
|
42786
|
+
orderBy: {
|
|
42787
|
+
field: "createdAt",
|
|
42788
|
+
direction: "desc"
|
|
42789
|
+
},
|
|
42790
|
+
limit: 1e3
|
|
42791
|
+
}
|
|
42792
|
+
});
|
|
42793
|
+
const out = [];
|
|
42794
|
+
for (const r of rows) {
|
|
42795
|
+
const parsed = this.parse(r.id, r.data);
|
|
42796
|
+
if (parsed !== null) out.push(parsed);
|
|
42797
|
+
}
|
|
42798
|
+
return out;
|
|
42799
|
+
}
|
|
42800
|
+
async setReviewed(id, reviewed) {
|
|
42801
|
+
await this.store.update.mutate({
|
|
42802
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42803
|
+
id,
|
|
42804
|
+
data: { reviewed }
|
|
42805
|
+
});
|
|
42806
|
+
}
|
|
42807
|
+
/** Release AND unsettle in one row write: the unpins the release implies
|
|
42808
|
+
* are owed from this moment, whether or not the caller lives to make them. */
|
|
42809
|
+
async setReleased(id, atMs) {
|
|
42810
|
+
await this.store.update.mutate({
|
|
42811
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42812
|
+
id,
|
|
42813
|
+
data: {
|
|
42814
|
+
releasedAt: atMs,
|
|
42815
|
+
pinsSettled: false
|
|
42816
|
+
}
|
|
42817
|
+
});
|
|
42818
|
+
}
|
|
42819
|
+
async setPinsSettled(id, settled) {
|
|
42820
|
+
await this.store.update.mutate({
|
|
42821
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42822
|
+
id,
|
|
42823
|
+
data: { pinsSettled: settled }
|
|
42824
|
+
});
|
|
42825
|
+
}
|
|
42826
|
+
async remove(id) {
|
|
42827
|
+
await this.store.delete.mutate({
|
|
42828
|
+
collection: TRACKING_TAPES_COLLECTION,
|
|
42829
|
+
key: id
|
|
42830
|
+
});
|
|
42831
|
+
}
|
|
42832
|
+
parse(id, data) {
|
|
42833
|
+
const ids = TrackIdsSchema.safeParse(decodeTrackIds(data["trackIds"]));
|
|
42834
|
+
const parsed = TrackingTapeRowSchema.safeParse({
|
|
42835
|
+
id,
|
|
42836
|
+
...data,
|
|
42837
|
+
scenario: freeText(data["scenario"]),
|
|
42838
|
+
note: freeText(data["note"] ?? null),
|
|
42839
|
+
releasedAt: data["releasedAt"] ?? null,
|
|
42840
|
+
trackIds: ids.success ? ids.data : null
|
|
42841
|
+
});
|
|
42842
|
+
if (parsed.success) return parsed.data;
|
|
42843
|
+
this.logger.warn("tracking tape index: skipped a malformed row", {
|
|
42844
|
+
meta: {
|
|
42845
|
+
id,
|
|
42846
|
+
issues: parsed.error.issues.map((i) => i.path.join(".")),
|
|
42847
|
+
trackIdsType: typeof data["trackIds"]
|
|
42848
|
+
},
|
|
42849
|
+
...typeof data["deviceId"] === "number" ? { tags: { deviceId: data["deviceId"] } } : {}
|
|
42850
|
+
});
|
|
42851
|
+
return null;
|
|
42852
|
+
}
|
|
42853
|
+
};
|
|
42854
|
+
//#endregion
|
|
42390
42855
|
//#region src/pipeline-analytics/debug/tape-actions.ts
|
|
42391
42856
|
/**
|
|
42392
42857
|
* `debug.tape.*` — the tracking-tape surface (D681, spec §3.3). Admin,
|
|
@@ -42417,12 +42882,19 @@ var TapeStatusRowSchema = object({
|
|
|
42417
42882
|
var TapeStatusInputSchema = object({ deviceIds: array(number().int()).optional() });
|
|
42418
42883
|
var TapeStatusOutputSchema = object({ cameras: array(TapeStatusRowSchema).readonly() });
|
|
42419
42884
|
var TapeLightingInputSchema = _enum(["day", "ir"]);
|
|
42885
|
+
/**
|
|
42886
|
+
* Free text the settings store would hand back decoded is refused by name
|
|
42887
|
+
* (follow-up 617, review M2): whole JSON array/object text in a TEXT column
|
|
42888
|
+
* comes back as an array or object, and the index could not read the row.
|
|
42889
|
+
*/
|
|
42890
|
+
var NOT_JSON_TEXT = "must not be JSON array/object text — the settings store would hand it back decoded";
|
|
42891
|
+
var notJsonText = (s) => !decodesAsJson(s);
|
|
42420
42892
|
var PromoteInputSchema = object({
|
|
42421
42893
|
deviceId: number().int().positive(),
|
|
42422
42894
|
fromMs: number().int(),
|
|
42423
42895
|
toMs: number().int(),
|
|
42424
|
-
scenario: string(),
|
|
42425
|
-
note: string().max(500).optional(),
|
|
42896
|
+
scenario: string().refine(notJsonText, { message: NOT_JSON_TEXT }),
|
|
42897
|
+
note: string().max(500).refine(notJsonText, { message: NOT_JSON_TEXT }).optional(),
|
|
42426
42898
|
lighting: TapeLightingInputSchema.optional()
|
|
42427
42899
|
});
|
|
42428
42900
|
var PromoteOutputSchema = discriminatedUnion("ok", [object({
|
|
@@ -50883,6 +51355,17 @@ function pairRiders(detections) {
|
|
|
50883
51355
|
* (D438) no frame did, and a newcomer on the stale prediction took the id.
|
|
50884
51356
|
*/
|
|
50885
51357
|
var MAX_PREDICTION_MS = 1e3;
|
|
51358
|
+
/**
|
|
51359
|
+
* A frame more than `maxMissedMs` behind the latest is a CLOCK STEP only as
|
|
51360
|
+
* the K-th such frame in a row, each in order with the one before (follow-up
|
|
51361
|
+
* 617, fix round 3; D49: a read that would destroy work needs another to
|
|
51362
|
+
* agree). A step ends every track on the camera; one stray timestamp — a
|
|
51363
|
+
* backlog spike 4.5 s late — or two runner clocks delivering interleaved
|
|
51364
|
+
* (a camera migrating between runners) must not. Three is the least that a
|
|
51365
|
+
* single late frame plus an interleaved partner cannot reach by chance; the
|
|
51366
|
+
* cost of a real step is K−1 dropped frames (~260 ms at 7.7 fps).
|
|
51367
|
+
*/
|
|
51368
|
+
var CLOCK_STEP_CONFIRM_FRAMES = 3;
|
|
50886
51369
|
/** Confident boxes kept on a tracklet's path (the old tracker's bound). */
|
|
50887
51370
|
var MAX_PATH_LENGTH = 300;
|
|
50888
51371
|
/** Swap test: crossed pairs cheaper than this over the chosen pairs are doubt. */
|
|
@@ -51688,6 +52171,35 @@ function linkReach(from, gapMs) {
|
|
|
51688
52171
|
return diag * Math.min(LINK_REACH_MAX_DIAGONALS, Math.max(LINK_REACH_MIN_DIAGONALS, LINK_REACH_DIAGONALS_PER_SEC * gapMs / 1e3));
|
|
51689
52172
|
}
|
|
51690
52173
|
var arcKey = (from, to) => `${from}\u0000${to}`;
|
|
52174
|
+
/**
|
|
52175
|
+
* The rejections that mean "geometry or appearance said no to a plausible
|
|
52176
|
+
* continuation" of a confirmed predecessor — a track the operator sees break
|
|
52177
|
+
* (follow-up 617, F5). Every one of them is past `hardRejection`, so the
|
|
52178
|
+
* predecessor is confirmed. NOT here (fix round 1): `unconfirmed`;
|
|
52179
|
+
* `has-successor` (continued elsewhere); `concurrent` and `identity-conflict`
|
|
52180
|
+
* (two subjects — the normal case, and the most frequent); `dropped` (an
|
|
52181
|
+
* operator delete or a refused chain).
|
|
52182
|
+
*/
|
|
52183
|
+
var BREAK_REJECTIONS = new Set([
|
|
52184
|
+
"reach",
|
|
52185
|
+
"area",
|
|
52186
|
+
"appearance",
|
|
52187
|
+
"contested",
|
|
52188
|
+
"cost"
|
|
52189
|
+
]);
|
|
52190
|
+
/** The confirmed predecessors whose link a decision rejected as a break (F5). */
|
|
52191
|
+
function confirmedPredecessorRejections(candidates) {
|
|
52192
|
+
const out = [];
|
|
52193
|
+
for (const c of candidates ?? []) {
|
|
52194
|
+
if (c.rejected === void 0 || !BREAK_REJECTIONS.has(c.rejected)) continue;
|
|
52195
|
+
out.push({
|
|
52196
|
+
from: c.from,
|
|
52197
|
+
rejected: c.rejected,
|
|
52198
|
+
gapMs: c.gapMs
|
|
52199
|
+
});
|
|
52200
|
+
}
|
|
52201
|
+
return out;
|
|
52202
|
+
}
|
|
51691
52203
|
function arcFacts(a) {
|
|
51692
52204
|
return {
|
|
51693
52205
|
from: a.from.id,
|
|
@@ -51765,6 +52277,26 @@ var DeferredLinker = class {
|
|
|
51765
52277
|
if (seen === void 0) this.names.set(p.id, new Set([p.identity]));
|
|
51766
52278
|
else seen.add(p.identity);
|
|
51767
52279
|
}
|
|
52280
|
+
/**
|
|
52281
|
+
* A clock step (follow-up 617, fix round 2): every piece still undecided is
|
|
52282
|
+
* decided `new`, so nothing that existed before the step can be absorbed
|
|
52283
|
+
* across it — whatever was ready was committed just before. The facade
|
|
52284
|
+
* rebases its clock past `LINK_MAX_GAP_MS`, so no later piece can continue
|
|
52285
|
+
* one of these either. An extra id, never a mix-up.
|
|
52286
|
+
*/
|
|
52287
|
+
closeUndecided(now, pieces) {
|
|
52288
|
+
for (const p of pieces) this.accept(p, now);
|
|
52289
|
+
for (const p of this.pieces.values()) {
|
|
52290
|
+
if (this.outcomes.has(p.id)) continue;
|
|
52291
|
+
this.decide(p.id, "new");
|
|
52292
|
+
this.observer?.({
|
|
52293
|
+
t: now,
|
|
52294
|
+
kind: "new",
|
|
52295
|
+
to: p.id,
|
|
52296
|
+
reason: "clock-step"
|
|
52297
|
+
});
|
|
52298
|
+
}
|
|
52299
|
+
}
|
|
51768
52300
|
isTailFinal(id, now, isLive) {
|
|
51769
52301
|
const root = this.rootOf(id);
|
|
51770
52302
|
if (this.pieces.has(root) && !this.outcomes.has(root)) return false;
|
|
@@ -52515,7 +53047,15 @@ var Tracklet = class {
|
|
|
52515
53047
|
h: this.assocBox.h
|
|
52516
53048
|
};
|
|
52517
53049
|
}
|
|
53050
|
+
/**
|
|
53051
|
+
* A confident sighting. One OLDER than the last (`t < lastSeenAt` — ~1 % of
|
|
53052
|
+
* frames reach the tracker out of capture order, up to 1.2 s back) is
|
|
53053
|
+
* `stale`: it changes nothing. A rewound tracklet would be measured from a
|
|
53054
|
+
* box its subject left a second ago, and L2 refused a link by reach on
|
|
53055
|
+
* exactly that (follow-up 617, F1). The caller reports it.
|
|
53056
|
+
*/
|
|
52518
53057
|
observeConfident(det, t, faceBearing) {
|
|
53058
|
+
if (t < this.lastSeenAt) return "stale";
|
|
52519
53059
|
const dt = t - this.lastSeenAt;
|
|
52520
53060
|
if (dt > 0) {
|
|
52521
53061
|
const from = centreOf(this.lastBox);
|
|
@@ -52527,6 +53067,7 @@ var Tracklet = class {
|
|
|
52527
53067
|
this.emitIntervalMs = dt;
|
|
52528
53068
|
}
|
|
52529
53069
|
this.recordConfident(det, t, faceBearing);
|
|
53070
|
+
return "recorded";
|
|
52530
53071
|
}
|
|
52531
53072
|
observeLowScore(box, t) {
|
|
52532
53073
|
this.assocBox = box;
|
|
@@ -52628,6 +53169,15 @@ var TrackletTracker = class {
|
|
|
52628
53169
|
demoteSilent(t) {
|
|
52629
53170
|
for (const tr of this.live.values()) if (tr.mode === "active" && t - tr.lastSeenAt > MAX_PREDICTION_MS) tr.mode = "coasting";
|
|
52630
53171
|
}
|
|
53172
|
+
/** Ends every live tracklet at its own last sighting (a clock step); the ids it ended. */
|
|
53173
|
+
endAll(reason) {
|
|
53174
|
+
const ended = [];
|
|
53175
|
+
for (const tr of [...this.live.values()]) {
|
|
53176
|
+
ended.push(tr.id);
|
|
53177
|
+
this.finish(tr, tr.lastSeenAt, reason);
|
|
53178
|
+
}
|
|
53179
|
+
return ended;
|
|
53180
|
+
}
|
|
52631
53181
|
/** Silence: end what the coast budget no longer covers. */
|
|
52632
53182
|
expire(now) {
|
|
52633
53183
|
for (const tr of [...this.live.values()]) if (now - tr.lastSeenAt > this.config.maxMissedMs) this.finish(tr, tr.lastSeenAt, "missed");
|
|
@@ -52718,12 +53268,12 @@ var TrackletTracker = class {
|
|
|
52718
53268
|
const d = frame.detections[pair.detection];
|
|
52719
53269
|
if (tr === void 0 || d === void 0) return;
|
|
52720
53270
|
frame.used.add(pair.detection);
|
|
53271
|
+
if (tr.observeConfident(d, frame.t, frame.ctx.faceBearing?.has(d.bbox) === true) === "stale") return;
|
|
52721
53272
|
tr.mode = "active";
|
|
52722
53273
|
if (d.class !== tr.klass) this.refuse({
|
|
52723
53274
|
reason: "cross-class-absorbed",
|
|
52724
53275
|
against: tr
|
|
52725
53276
|
}, d);
|
|
52726
|
-
tr.observeConfident(d, frame.t, frame.ctx.faceBearing?.has(d.bbox) === true);
|
|
52727
53277
|
if (pair.contested) {
|
|
52728
53278
|
tr.contestedHits += 1;
|
|
52729
53279
|
tr.contestedSince ??= frame.t;
|
|
@@ -52912,27 +53462,151 @@ var TrackerV2 = class {
|
|
|
52912
53462
|
l1;
|
|
52913
53463
|
l2;
|
|
52914
53464
|
pending = [];
|
|
53465
|
+
/**
|
|
53466
|
+
* Internal time = the caller's frame time + `offset`. Zero until a clock
|
|
53467
|
+
* step; each step moves it so the internal clock never goes back and
|
|
53468
|
+
* resumes past `LINK_MAX_GAP_MS` of silence (fix round 2, C1). L1, L2, their
|
|
53469
|
+
* facts and prunes run on internal time; fusions and `frameClock()` are
|
|
53470
|
+
* handed back in the caller's clock.
|
|
53471
|
+
*/
|
|
53472
|
+
offset = 0;
|
|
52915
53473
|
lastT = 0;
|
|
53474
|
+
/** The latest FRAME time taken (internal) — never the sweep's extrapolated `now`. */
|
|
53475
|
+
lastFrameT = Number.NEGATIVE_INFINITY;
|
|
53476
|
+
observers;
|
|
53477
|
+
/** Caller times of the consecutive frames past the stale bound so far — the
|
|
53478
|
+
* run a clock step needs `CLOCK_STEP_CONFIRM_FRAMES` of (fix round 3). */
|
|
53479
|
+
stepRun = [];
|
|
52916
53480
|
constructor(config, deps) {
|
|
52917
53481
|
this.config = config;
|
|
52918
|
-
this.
|
|
53482
|
+
this.observers = deps.observers ?? {};
|
|
53483
|
+
const out = this.observers;
|
|
53484
|
+
const internal = {
|
|
53485
|
+
...out.refusal !== void 0 ? { refusal: out.refusal } : {},
|
|
53486
|
+
ended: (f) => out.ended?.({
|
|
53487
|
+
...f,
|
|
53488
|
+
at: f.at - this.offset
|
|
53489
|
+
}),
|
|
53490
|
+
split: (f) => out.split?.({
|
|
53491
|
+
...f,
|
|
53492
|
+
t: f.t - this.offset
|
|
53493
|
+
})
|
|
53494
|
+
};
|
|
53495
|
+
this.l1 = new TrackletTracker(config, deps.mintTrackId, internal);
|
|
52919
53496
|
this.l2 = new DeferredLinker({
|
|
52920
53497
|
maxMissedMs: config.maxMissedMs,
|
|
52921
53498
|
appearanceResolvesAmbiguity: config.appearanceResolvesAmbiguity
|
|
52922
|
-
},
|
|
53499
|
+
}, (f) => out.link?.({
|
|
53500
|
+
...f,
|
|
53501
|
+
t: f.t - this.offset
|
|
53502
|
+
}));
|
|
52923
53503
|
}
|
|
52924
|
-
update(detections,
|
|
53504
|
+
update(detections, frameT, ctx) {
|
|
53505
|
+
const order = this.classifyFrame(frameT);
|
|
53506
|
+
if (order === "out-of-order") return this.drop(frameT, detections.length);
|
|
53507
|
+
let t = frameT + this.offset;
|
|
53508
|
+
if (order === "clock-step") t = this.clockStep(frameT, this.lastFrameT - t);
|
|
53509
|
+
this.stepRun = [];
|
|
53510
|
+
this.lastFrameT = t;
|
|
52925
53511
|
this.lastT = Math.max(this.lastT, t);
|
|
52926
53512
|
const step = this.l1.step(detections, t, ctx);
|
|
52927
53513
|
this.link(this.lastT);
|
|
52928
53514
|
return step.emissions.map((e) => this.emit(e, t));
|
|
52929
53515
|
}
|
|
52930
53516
|
/**
|
|
53517
|
+
* A frame stamped before the latest one already taken (follow-up 617, F1;
|
|
53518
|
+
* ~1 % of frames, up to 1.2 s back). It is decided ONCE, for the whole
|
|
53519
|
+
* frame (fix round 1, H1): matched against the tracklets' NEWEST boxes, a
|
|
53520
|
+
* stale box can land on another subject — a walker's old box on the walker
|
|
53521
|
+
* behind — and be emitted under that id. So nothing binds, spawns, splits or
|
|
53522
|
+
* is demoted on it; the confirmed tracklets are emitted as on a frame that
|
|
53523
|
+
* matched nothing (no sighting), and one fact says the frame was dropped.
|
|
53524
|
+
*/
|
|
53525
|
+
/**
|
|
53526
|
+
* How `update` will take a frame at `frameT` — without taking it. The
|
|
53527
|
+
* processor asks first (fix round 3, MEDIUM-2), so a dropped frame never
|
|
53528
|
+
* reaches the stationary gate, the zones or the event path.
|
|
53529
|
+
* - `in-order`: not behind the latest frame taken;
|
|
53530
|
+
* - `out-of-order`: behind it — within `maxMissedMs` (disorder, ≤ 1.2 s
|
|
53531
|
+
* measured), or further back but not yet the `CLOCK_STEP_CONFIRM_FRAMES`-th
|
|
53532
|
+
* such frame in a row;
|
|
53533
|
+
* - `clock-step`: the K-th consecutive frame past the bound, each in order
|
|
53534
|
+
* with the one before (within `maxMissedMs`).
|
|
53535
|
+
*/
|
|
53536
|
+
classifyFrame(frameT) {
|
|
53537
|
+
const behindMs = this.lastFrameT - (frameT + this.offset);
|
|
53538
|
+
if (!(behindMs > 0)) return "in-order";
|
|
53539
|
+
if (behindMs <= this.config.maxMissedMs) return "out-of-order";
|
|
53540
|
+
return this.runLength(frameT) >= CLOCK_STEP_CONFIRM_FRAMES ? "clock-step" : "out-of-order";
|
|
53541
|
+
}
|
|
53542
|
+
/** The past-bound run this frame would make, itself included. */
|
|
53543
|
+
runLength(frameT) {
|
|
53544
|
+
const prev = this.stepRun.at(-1);
|
|
53545
|
+
return (prev !== void 0 && frameT >= prev && frameT - prev <= this.config.maxMissedMs ? this.stepRun.length : 0) + 1;
|
|
53546
|
+
}
|
|
53547
|
+
/**
|
|
53548
|
+
* Drops a frame `classifyFrame` called `out-of-order`. A frame past the
|
|
53549
|
+
* bound extends (or restarts) the run a clock step needs; any other breaks
|
|
53550
|
+
* it. One fact per frame.
|
|
53551
|
+
*/
|
|
53552
|
+
drop(frameT, detections) {
|
|
53553
|
+
const behindMs = this.lastFrameT - (frameT + this.offset);
|
|
53554
|
+
let pendingClockStep = 0;
|
|
53555
|
+
if (behindMs > this.config.maxMissedMs) {
|
|
53556
|
+
pendingClockStep = this.runLength(frameT);
|
|
53557
|
+
this.stepRun = pendingClockStep === 1 ? [frameT] : [...this.stepRun, frameT];
|
|
53558
|
+
} else this.stepRun = [];
|
|
53559
|
+
this.observers.outOfOrder?.({
|
|
53560
|
+
t: frameT,
|
|
53561
|
+
lastFrameT: this.frameClock(),
|
|
53562
|
+
behindMs,
|
|
53563
|
+
detections,
|
|
53564
|
+
pendingClockStep
|
|
53565
|
+
});
|
|
53566
|
+
return this.l1.liveTracklets().filter((tr) => tr.confirmed).map((tracklet) => this.emit({
|
|
53567
|
+
tracklet,
|
|
53568
|
+
detection: void 0,
|
|
53569
|
+
matched: false
|
|
53570
|
+
}, this.lastFrameT));
|
|
53571
|
+
}
|
|
53572
|
+
/**
|
|
53573
|
+
* The frame clock stepped back further than `maxMissedMs` (fix round 2, C1):
|
|
53574
|
+
* not disorder — an NTP correction, or a camera moved to a runner whose clock
|
|
53575
|
+
* is behind. An unbounded gate dropped every frame until the new clock passed
|
|
53576
|
+
* the old high mark (60 s back: 461 frames, a new subject never tracked, the
|
|
53577
|
+
* departed one coasting on). Instead: commit what L2 has decided, end every
|
|
53578
|
+
* live tracklet at its own last sighting, decide every piece still undecided
|
|
53579
|
+
* `new` (no fusion crosses the step), and resume the internal clock past
|
|
53580
|
+
* `LINK_MAX_GAP_MS` of silence, so nothing later can continue anything
|
|
53581
|
+
* earlier. Returns the frame's internal time.
|
|
53582
|
+
*/
|
|
53583
|
+
clockStep(frameT, behindMs) {
|
|
53584
|
+
const lastFrameT = this.frameClock();
|
|
53585
|
+
this.link(this.lastT);
|
|
53586
|
+
const ended = this.l1.endAll("clock-step");
|
|
53587
|
+
this.l2.closeUndecided(this.lastT, this.pieces());
|
|
53588
|
+
const resumeAt = Math.max(this.lastT, this.lastFrameT) + LINK_MAX_GAP_MS + 1;
|
|
53589
|
+
this.offset = resumeAt - frameT;
|
|
53590
|
+
this.lastT = resumeAt;
|
|
53591
|
+
this.observers.clockStep?.({
|
|
53592
|
+
t: frameT,
|
|
53593
|
+
lastFrameT,
|
|
53594
|
+
behindMs,
|
|
53595
|
+
ended
|
|
53596
|
+
});
|
|
53597
|
+
return resumeAt;
|
|
53598
|
+
}
|
|
53599
|
+
/** The latest frame time taken, in the caller's clock (rebased by a clock step). */
|
|
53600
|
+
frameClock() {
|
|
53601
|
+
return this.lastFrameT - this.offset;
|
|
53602
|
+
}
|
|
53603
|
+
/**
|
|
52931
53604
|
* The close sweep's entry on a camera whose frames stopped (D438): end the
|
|
52932
53605
|
* tracklets the coast budget no longer covers, THEN run L2 (review C2).
|
|
52933
53606
|
* `now` is FRAME time (the processor converts wall clock, Task 11).
|
|
52934
53607
|
*/
|
|
52935
|
-
advance(
|
|
53608
|
+
advance(frameNow) {
|
|
53609
|
+
const now = frameNow + this.offset;
|
|
52936
53610
|
this.lastT = Math.max(this.lastT, now);
|
|
52937
53611
|
this.l1.expire(now);
|
|
52938
53612
|
this.link(now);
|
|
@@ -52948,6 +53622,7 @@ var TrackerV2 = class {
|
|
|
52948
53622
|
markSuppressed(id) {
|
|
52949
53623
|
this.l1.markSuppressed(id);
|
|
52950
53624
|
}
|
|
53625
|
+
/** In the caller's clock (translated when each was committed, LOW-5). */
|
|
52951
53626
|
drainFusions() {
|
|
52952
53627
|
const out = this.pending;
|
|
52953
53628
|
this.pending = [];
|
|
@@ -52960,8 +53635,8 @@ var TrackerV2 = class {
|
|
|
52960
53635
|
isLive(id) {
|
|
52961
53636
|
return this.l2.membersOf(this.l2.rootOf(id)).some((m) => this.isLiveTracklet(m));
|
|
52962
53637
|
}
|
|
52963
|
-
isTailFinal(id,
|
|
52964
|
-
return this.l2.isTailFinal(id,
|
|
53638
|
+
isTailFinal(id, frameNow) {
|
|
53639
|
+
return this.l2.isTailFinal(id, frameNow + this.offset, (m) => this.isLiveTracklet(m));
|
|
52965
53640
|
}
|
|
52966
53641
|
/**
|
|
52967
53642
|
* Ends every live member of the chain. `stationary` (round 4): the
|
|
@@ -52977,9 +53652,15 @@ var TrackerV2 = class {
|
|
|
52977
53652
|
const tr = this.l1.get(id);
|
|
52978
53653
|
return tr !== void 0 && tr.endedAt === null;
|
|
52979
53654
|
}
|
|
53655
|
+
pieces() {
|
|
53656
|
+
return [...this.l1.liveTracklets(), ...this.l1.endedTracklets()].map(toPiece);
|
|
53657
|
+
}
|
|
52980
53658
|
link(now) {
|
|
52981
|
-
const
|
|
52982
|
-
|
|
53659
|
+
const offset = this.offset;
|
|
53660
|
+
for (const f of this.l2.step(now, this.pieces())) this.pending.push({
|
|
53661
|
+
...f,
|
|
53662
|
+
t: f.t - offset
|
|
53663
|
+
});
|
|
52983
53664
|
this.l1.prune(now);
|
|
52984
53665
|
this.l2.prune(now);
|
|
52985
53666
|
}
|
|
@@ -53042,7 +53723,8 @@ var NO_ENDS = {
|
|
|
53042
53723
|
missed: 0,
|
|
53043
53724
|
ambiguous: 0,
|
|
53044
53725
|
dropped: 0,
|
|
53045
|
-
stationary: 0
|
|
53726
|
+
stationary: 0,
|
|
53727
|
+
"clock-step": 0
|
|
53046
53728
|
};
|
|
53047
53729
|
function emptyStats() {
|
|
53048
53730
|
return {
|
|
@@ -53055,16 +53737,28 @@ function emptyStats() {
|
|
|
53055
53737
|
linksRefused: 0,
|
|
53056
53738
|
linksNew: 0,
|
|
53057
53739
|
linksDisagreed: 0,
|
|
53058
|
-
lowScoreHolds: 0
|
|
53740
|
+
lowScoreHolds: 0,
|
|
53741
|
+
outOfOrderFrames: 0,
|
|
53742
|
+
clockSteps: 0
|
|
53059
53743
|
};
|
|
53060
53744
|
}
|
|
53061
53745
|
/** Nothing happened in the window: no tracklet ended, no split, no L2
|
|
53062
53746
|
* decision, no low-score hold (review LOW-N1 — what the sweep stays quiet about). */
|
|
53063
53747
|
function isIdleTrackerStats(stats) {
|
|
53064
|
-
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;
|
|
53748
|
+
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;
|
|
53065
53749
|
}
|
|
53066
53750
|
/** Shared empty — a fold is rare and every other frame would allocate. */
|
|
53067
53751
|
var EMPTY_RIDER_FOLDS = [];
|
|
53752
|
+
/** The tape observation of a dropped frame: no gate verdict, nothing bound. */
|
|
53753
|
+
var DROPPED_FRAME_OBSERVATION = {
|
|
53754
|
+
gate: {
|
|
53755
|
+
strong: [],
|
|
53756
|
+
weak: [],
|
|
53757
|
+
confirmed: [],
|
|
53758
|
+
woken: []
|
|
53759
|
+
},
|
|
53760
|
+
live: []
|
|
53761
|
+
};
|
|
53068
53762
|
/** Mapping from StateAnalyzer's `ObjectState.state` values to the
|
|
53069
53763
|
* canonical TrackState enum used on tracks + events. */
|
|
53070
53764
|
function mapObjectStateToTrackState(s) {
|
|
@@ -53243,6 +53937,14 @@ var FrameProcessor = class {
|
|
|
53243
53937
|
else if (f.kind === "new") this.stats.linksNew += 1;
|
|
53244
53938
|
else if (f.kind === "disagreed") this.stats.linksDisagreed += 1;
|
|
53245
53939
|
this.trackerObservers.link?.(f);
|
|
53940
|
+
},
|
|
53941
|
+
outOfOrder: (f) => {
|
|
53942
|
+
this.stats.outOfOrderFrames += 1;
|
|
53943
|
+
this.trackerObservers.outOfOrder?.(f);
|
|
53944
|
+
},
|
|
53945
|
+
clockStep: (f) => {
|
|
53946
|
+
this.stats.clockSteps += 1;
|
|
53947
|
+
this.trackerObservers.clockStep?.(f);
|
|
53246
53948
|
}
|
|
53247
53949
|
}
|
|
53248
53950
|
});
|
|
@@ -53526,6 +54228,37 @@ var FrameProcessor = class {
|
|
|
53526
54228
|
* Zero is the expected reading; a rising one means two people stand close
|
|
53527
54229
|
* together on this camera, which is worth knowing per-camera rather than
|
|
53528
54230
|
* per-fleet. */
|
|
54231
|
+
/**
|
|
54232
|
+
* A frame `classifyFrame` called out of order: the tracker records the drop
|
|
54233
|
+
* (and any clock-step run it extends) and nothing else reads the frame. The
|
|
54234
|
+
* per-frame side tables a reader asks for afterwards are emptied, so it never
|
|
54235
|
+
* re-reads the previous frame's.
|
|
54236
|
+
*/
|
|
54237
|
+
droppedFrame(input) {
|
|
54238
|
+
this.tracker.drop(input.timestamp, input.frame.detections.length);
|
|
54239
|
+
this.lastZoneOverlaps = /* @__PURE__ */ new Map();
|
|
54240
|
+
this.lastZoneRejections = /* @__PURE__ */ new Map();
|
|
54241
|
+
this.lastLabelWriteRefusals = [];
|
|
54242
|
+
const fusions = this.tracker.drainFusions();
|
|
54243
|
+
this.applyFusions(fusions);
|
|
54244
|
+
return {
|
|
54245
|
+
deviceId: this.deviceId,
|
|
54246
|
+
timestamp: input.timestamp,
|
|
54247
|
+
frameWidth: input.frame.width,
|
|
54248
|
+
frameHeight: input.frame.height,
|
|
54249
|
+
tracked: [],
|
|
54250
|
+
objectEvents: [],
|
|
54251
|
+
appearanceEvents: [],
|
|
54252
|
+
rawTrackedDetections: [],
|
|
54253
|
+
stationaryConfirmed: [],
|
|
54254
|
+
stationaryWoken: [],
|
|
54255
|
+
stationaryContinued: 0,
|
|
54256
|
+
riderFolds: [],
|
|
54257
|
+
...input.observe === true ? { observation: DROPPED_FRAME_OBSERVATION } : {},
|
|
54258
|
+
fusions,
|
|
54259
|
+
droppedOutOfOrder: true
|
|
54260
|
+
};
|
|
54261
|
+
}
|
|
53529
54262
|
facesRefusedOutsideParent() {
|
|
53530
54263
|
return this.faceOutsideParentCount;
|
|
53531
54264
|
}
|
|
@@ -53548,6 +54281,7 @@ var FrameProcessor = class {
|
|
|
53548
54281
|
}
|
|
53549
54282
|
process(input) {
|
|
53550
54283
|
const { timestamp, frame } = input;
|
|
54284
|
+
if (this.tracker.classifyFrame(timestamp) === "out-of-order") return this.droppedFrame(input);
|
|
53551
54285
|
this.lastFrameT = timestamp;
|
|
53552
54286
|
this.lastFrameWallAt = this.wallClock();
|
|
53553
54287
|
const frameWidth = frame.width;
|
|
@@ -56299,239 +57033,6 @@ async function promoteTapeWindow(deps, input, signal) {
|
|
|
56299
57033
|
};
|
|
56300
57034
|
}
|
|
56301
57035
|
//#endregion
|
|
56302
|
-
//#region src/pipeline-analytics/store/tracking-tape-index-store.ts
|
|
56303
|
-
/**
|
|
56304
|
-
* @durable class=ledger owner=pipeline-analytics
|
|
56305
|
-
* write="one row per PROMOTED tracking-tape window (`debug.tape.promote`);
|
|
56306
|
-
* updated when the operator marks it reviewed or releases its pins;
|
|
56307
|
-
* deleted together with its file by `debug.tape.delete`."
|
|
56308
|
-
* retention="NOT an age clock and NOT track retention — a promoted window is
|
|
56309
|
-
* history being labelled (D681). Bounded by the promotion budget
|
|
56310
|
-
* (MAX_PROMOTED_TAPES = 60, 1 GiB): a promotion beyond it is REFUSED,
|
|
56311
|
-
* never satisfied by evicting another row."
|
|
56312
|
-
*/
|
|
56313
|
-
var TRACKING_TAPES_COLLECTION = "pipeline-analytics:tracking-tapes";
|
|
56314
|
-
var TRACKING_TAPES_COLUMNS = [
|
|
56315
|
-
{
|
|
56316
|
-
name: "id",
|
|
56317
|
-
type: "TEXT",
|
|
56318
|
-
primaryKey: true,
|
|
56319
|
-
notNull: true
|
|
56320
|
-
},
|
|
56321
|
-
{
|
|
56322
|
-
name: "deviceId",
|
|
56323
|
-
type: "INTEGER",
|
|
56324
|
-
notNull: true
|
|
56325
|
-
},
|
|
56326
|
-
{
|
|
56327
|
-
name: "scenario",
|
|
56328
|
-
type: "TEXT",
|
|
56329
|
-
notNull: true
|
|
56330
|
-
},
|
|
56331
|
-
{
|
|
56332
|
-
name: "note",
|
|
56333
|
-
type: "TEXT"
|
|
56334
|
-
},
|
|
56335
|
-
{
|
|
56336
|
-
name: "lighting",
|
|
56337
|
-
type: "TEXT",
|
|
56338
|
-
notNull: true
|
|
56339
|
-
},
|
|
56340
|
-
{
|
|
56341
|
-
name: "fromMs",
|
|
56342
|
-
type: "INTEGER",
|
|
56343
|
-
notNull: true
|
|
56344
|
-
},
|
|
56345
|
-
{
|
|
56346
|
-
name: "toMs",
|
|
56347
|
-
type: "INTEGER",
|
|
56348
|
-
notNull: true
|
|
56349
|
-
},
|
|
56350
|
-
{
|
|
56351
|
-
name: "state",
|
|
56352
|
-
type: "TEXT",
|
|
56353
|
-
notNull: true
|
|
56354
|
-
},
|
|
56355
|
-
{
|
|
56356
|
-
name: "frames",
|
|
56357
|
-
type: "INTEGER",
|
|
56358
|
-
notNull: true
|
|
56359
|
-
},
|
|
56360
|
-
{
|
|
56361
|
-
name: "skips",
|
|
56362
|
-
type: "INTEGER",
|
|
56363
|
-
notNull: true
|
|
56364
|
-
},
|
|
56365
|
-
{
|
|
56366
|
-
name: "gaps",
|
|
56367
|
-
type: "INTEGER",
|
|
56368
|
-
notNull: true
|
|
56369
|
-
},
|
|
56370
|
-
{
|
|
56371
|
-
name: "bytes",
|
|
56372
|
-
type: "INTEGER",
|
|
56373
|
-
notNull: true
|
|
56374
|
-
},
|
|
56375
|
-
{
|
|
56376
|
-
name: "reviewed",
|
|
56377
|
-
type: "BOOLEAN",
|
|
56378
|
-
notNull: true
|
|
56379
|
-
},
|
|
56380
|
-
{
|
|
56381
|
-
name: "trackIds",
|
|
56382
|
-
type: "TEXT",
|
|
56383
|
-
notNull: true
|
|
56384
|
-
},
|
|
56385
|
-
{
|
|
56386
|
-
name: "createdAt",
|
|
56387
|
-
type: "INTEGER",
|
|
56388
|
-
notNull: true
|
|
56389
|
-
},
|
|
56390
|
-
{
|
|
56391
|
-
name: "releasedAt",
|
|
56392
|
-
type: "INTEGER"
|
|
56393
|
-
},
|
|
56394
|
-
{
|
|
56395
|
-
name: "pinsSettled",
|
|
56396
|
-
type: "BOOLEAN",
|
|
56397
|
-
notNull: true
|
|
56398
|
-
}
|
|
56399
|
-
];
|
|
56400
|
-
var TrackingTapeRowSchema = object({
|
|
56401
|
-
id: string(),
|
|
56402
|
-
deviceId: number().int(),
|
|
56403
|
-
scenario: string(),
|
|
56404
|
-
note: string().nullable(),
|
|
56405
|
-
lighting: TapeLightingSchema,
|
|
56406
|
-
fromMs: number().int(),
|
|
56407
|
-
toMs: number().int(),
|
|
56408
|
-
state: _enum(["complete", "incomplete"]),
|
|
56409
|
-
frames: number().int(),
|
|
56410
|
-
skips: number().int(),
|
|
56411
|
-
gaps: number().int(),
|
|
56412
|
-
bytes: number().int(),
|
|
56413
|
-
reviewed: boolean(),
|
|
56414
|
-
trackIds: array(string()),
|
|
56415
|
-
createdAt: number().int(),
|
|
56416
|
-
releasedAt: number().int().nullable(),
|
|
56417
|
-
pinsSettled: boolean()
|
|
56418
|
-
});
|
|
56419
|
-
var TrackIdsSchema = array(string());
|
|
56420
|
-
var TrackingTapeIndexStore = class {
|
|
56421
|
-
store;
|
|
56422
|
-
logger;
|
|
56423
|
-
constructor(deps) {
|
|
56424
|
-
this.store = deps.store;
|
|
56425
|
-
this.logger = deps.logger;
|
|
56426
|
-
}
|
|
56427
|
-
static async declare(store) {
|
|
56428
|
-
await store.declareCollection.mutate({
|
|
56429
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56430
|
-
columns: [...TRACKING_TAPES_COLUMNS],
|
|
56431
|
-
indexes: [{
|
|
56432
|
-
name: "idx_trackingtapes_device_created",
|
|
56433
|
-
columns: ["deviceId", "createdAt"]
|
|
56434
|
-
}]
|
|
56435
|
-
});
|
|
56436
|
-
}
|
|
56437
|
-
async insert(row) {
|
|
56438
|
-
const { id, trackIds, ...rest } = TrackingTapeRowSchema.parse(row);
|
|
56439
|
-
await this.store.insert.mutate({
|
|
56440
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56441
|
-
record: {
|
|
56442
|
-
id,
|
|
56443
|
-
data: {
|
|
56444
|
-
...rest,
|
|
56445
|
-
trackIds: JSON.stringify(trackIds)
|
|
56446
|
-
}
|
|
56447
|
-
}
|
|
56448
|
-
});
|
|
56449
|
-
}
|
|
56450
|
-
async get(id) {
|
|
56451
|
-
const first = (await this.store.query.query({
|
|
56452
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56453
|
-
filter: {
|
|
56454
|
-
where: { id },
|
|
56455
|
-
limit: 1
|
|
56456
|
-
}
|
|
56457
|
-
}))[0];
|
|
56458
|
-
return first === void 0 ? null : this.parse(first.id, first.data);
|
|
56459
|
-
}
|
|
56460
|
-
async list(deviceId) {
|
|
56461
|
-
const rows = await this.store.query.query({
|
|
56462
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56463
|
-
filter: {
|
|
56464
|
-
...deviceId !== void 0 ? { where: { deviceId } } : {},
|
|
56465
|
-
orderBy: {
|
|
56466
|
-
field: "createdAt",
|
|
56467
|
-
direction: "desc"
|
|
56468
|
-
},
|
|
56469
|
-
limit: 1e3
|
|
56470
|
-
}
|
|
56471
|
-
});
|
|
56472
|
-
const out = [];
|
|
56473
|
-
for (const r of rows) {
|
|
56474
|
-
const parsed = this.parse(r.id, r.data);
|
|
56475
|
-
if (parsed !== null) out.push(parsed);
|
|
56476
|
-
}
|
|
56477
|
-
return out;
|
|
56478
|
-
}
|
|
56479
|
-
async setReviewed(id, reviewed) {
|
|
56480
|
-
await this.store.update.mutate({
|
|
56481
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56482
|
-
id,
|
|
56483
|
-
data: { reviewed }
|
|
56484
|
-
});
|
|
56485
|
-
}
|
|
56486
|
-
/** Release AND unsettle in one row write: the unpins the release implies
|
|
56487
|
-
* are owed from this moment, whether or not the caller lives to make them. */
|
|
56488
|
-
async setReleased(id, atMs) {
|
|
56489
|
-
await this.store.update.mutate({
|
|
56490
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56491
|
-
id,
|
|
56492
|
-
data: {
|
|
56493
|
-
releasedAt: atMs,
|
|
56494
|
-
pinsSettled: false
|
|
56495
|
-
}
|
|
56496
|
-
});
|
|
56497
|
-
}
|
|
56498
|
-
async setPinsSettled(id, settled) {
|
|
56499
|
-
await this.store.update.mutate({
|
|
56500
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56501
|
-
id,
|
|
56502
|
-
data: { pinsSettled: settled }
|
|
56503
|
-
});
|
|
56504
|
-
}
|
|
56505
|
-
async remove(id) {
|
|
56506
|
-
await this.store.delete.mutate({
|
|
56507
|
-
collection: TRACKING_TAPES_COLLECTION,
|
|
56508
|
-
key: id
|
|
56509
|
-
});
|
|
56510
|
-
}
|
|
56511
|
-
parse(id, data) {
|
|
56512
|
-
let trackIds = [];
|
|
56513
|
-
try {
|
|
56514
|
-
trackIds = JSON.parse(String(data["trackIds"] ?? "[]"));
|
|
56515
|
-
} catch {
|
|
56516
|
-
trackIds = null;
|
|
56517
|
-
}
|
|
56518
|
-
const ids = TrackIdsSchema.safeParse(trackIds);
|
|
56519
|
-
const parsed = TrackingTapeRowSchema.safeParse({
|
|
56520
|
-
id,
|
|
56521
|
-
...data,
|
|
56522
|
-
note: data["note"] ?? null,
|
|
56523
|
-
releasedAt: data["releasedAt"] ?? null,
|
|
56524
|
-
trackIds: ids.success ? ids.data : null
|
|
56525
|
-
});
|
|
56526
|
-
if (parsed.success) return parsed.data;
|
|
56527
|
-
this.logger.warn("tracking tape index: skipped a malformed row", {
|
|
56528
|
-
meta: { id },
|
|
56529
|
-
...typeof data["deviceId"] === "number" ? { tags: { deviceId: data["deviceId"] } } : {}
|
|
56530
|
-
});
|
|
56531
|
-
return null;
|
|
56532
|
-
}
|
|
56533
|
-
};
|
|
56534
|
-
//#endregion
|
|
56535
57036
|
//#region src/pipeline-analytics/tracking-bench/recorder/tape-status.ts
|
|
56536
57037
|
/** The one node that may hold tapes: sqlite and bulk state are hub-only, and
|
|
56537
57038
|
* an agent persists nothing (spec §3.1). */
|
|
@@ -96033,6 +96534,30 @@ async function encodeClipQuery(encode, text, modelId, warn) {
|
|
|
96033
96534
|
}
|
|
96034
96535
|
}
|
|
96035
96536
|
//#endregion
|
|
96537
|
+
//#region src/pipeline-analytics/nc-owner-media-reader.ts
|
|
96538
|
+
/** Keep only the rows written for THIS generation of the event id. */
|
|
96539
|
+
function ofGeneration(files, scope) {
|
|
96540
|
+
return files.filter((f) => f.timestamp === scope.recordAt);
|
|
96541
|
+
}
|
|
96542
|
+
function makeNcOwnerMediaReader(deps) {
|
|
96543
|
+
return async (ownerKind, ownerId, eventScope) => {
|
|
96544
|
+
if (ownerKind === "track") return deps.media.listByOwner("track", await deps.resolveTrackOwnerId(ownerId));
|
|
96545
|
+
if (eventScope === void 0) return deps.media.listByOwner("object", ownerId);
|
|
96546
|
+
const onCamera = await deps.media.listByOwner("object", ownerId, eventScope.deviceId);
|
|
96547
|
+
const kept = ofGeneration(onCamera, eventScope);
|
|
96548
|
+
if (kept.length < onCamera.length) deps.logger.warn("event media of another record on a reused event id — not used", {
|
|
96549
|
+
tags: { deviceId: eventScope.deviceId },
|
|
96550
|
+
meta: {
|
|
96551
|
+
ownerId,
|
|
96552
|
+
recordAt: eventScope.recordAt,
|
|
96553
|
+
refused: onCamera.length - kept.length,
|
|
96554
|
+
kept: kept.length
|
|
96555
|
+
}
|
|
96556
|
+
});
|
|
96557
|
+
return kept;
|
|
96558
|
+
};
|
|
96559
|
+
}
|
|
96560
|
+
//#endregion
|
|
96036
96561
|
//#region src/pipeline-analytics/store/settings-raw-mirror.ts
|
|
96037
96562
|
/** How long a mirrored entry is served before a refresh is started behind the
|
|
96038
96563
|
* next reader. See the module doc for why this matches the per-kind caches'
|
|
@@ -97387,6 +97912,12 @@ var TRACKER_FACT_LOG_SAMPLE_EVERY = 10;
|
|
|
97387
97912
|
* positive spawns (review M1: ~1 750 `new` decisions per camera per hour) —
|
|
97388
97913
|
* are sampled harder; the per-minute summary carries their exact counts. */
|
|
97389
97914
|
var TRACKER_PIECE_LOG_SAMPLE_EVERY = 50;
|
|
97915
|
+
/** Out-of-order frames (follow-up 617, F1: ~1 % of frames) log the first N,
|
|
97916
|
+
* then one in this many — every line carries the count; the per-minute
|
|
97917
|
+
* summary carries the exact `outOfOrderFrames`. */
|
|
97918
|
+
var TRACKER_OUT_OF_ORDER_LOG_SAMPLE_EVERY = 50;
|
|
97919
|
+
/** Clock-step WARNs past the first ten (fix round 3, MEDIUM-1). */
|
|
97920
|
+
var TRACKER_CLOCK_STEP_LOG_SAMPLE_EVERY = 50;
|
|
97390
97921
|
/** First N, then one in `every` — the refusal stream's cadence (D391). */
|
|
97391
97922
|
function sampledTrackerFact(count, every = TRACKER_FACT_LOG_SAMPLE_EVERY) {
|
|
97392
97923
|
return count <= TRACKER_FACT_LOG_ALWAYS_FIRST || count % every === 0;
|
|
@@ -100357,7 +100888,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
100357
100888
|
}
|
|
100358
100889
|
});
|
|
100359
100890
|
},
|
|
100360
|
-
getMediaForOwner:
|
|
100891
|
+
getMediaForOwner: makeNcOwnerMediaReader({
|
|
100892
|
+
media: stores.mediaStore,
|
|
100893
|
+
resolveTrackOwnerId: (trackId) => resolveMediaOwnerId("track", trackId, (id) => this.trackAliasStore?.resolve(id) ?? Promise.resolve(null), (failure) => this.aliasResolveFailureLogger?.(failure), "getMediaForOwner"),
|
|
100894
|
+
logger: this.ctx.logger.child("nc-media")
|
|
100895
|
+
}),
|
|
100361
100896
|
getSubjectTrail: async (deviceId, trackId) => {
|
|
100362
100897
|
const dims = this.lastFrameDimsByDevice.get(deviceId);
|
|
100363
100898
|
if (dims === void 0 || dims.w <= 0 || dims.h <= 0) return [];
|
|
@@ -102030,9 +102565,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
102030
102565
|
let splitCount = 0;
|
|
102031
102566
|
let ignoredCount = 0;
|
|
102032
102567
|
let newCount = 0;
|
|
102568
|
+
let outOfOrderCount = 0;
|
|
102569
|
+
let clockStepCount = 0;
|
|
102033
102570
|
const endedCounts = /* @__PURE__ */ new Map();
|
|
102034
102571
|
const logLink = (f) => {
|
|
102035
|
-
const
|
|
102572
|
+
const decision = {
|
|
102036
102573
|
source,
|
|
102037
102574
|
t: f.t,
|
|
102038
102575
|
kind: f.kind,
|
|
@@ -102041,7 +102578,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
102041
102578
|
cost: f.cost,
|
|
102042
102579
|
gapMs: f.gapMs,
|
|
102043
102580
|
reason: f.reason,
|
|
102044
|
-
recognition: f.recognition
|
|
102581
|
+
recognition: f.recognition
|
|
102582
|
+
};
|
|
102583
|
+
const meta = {
|
|
102584
|
+
...decision,
|
|
102045
102585
|
candidates: f.candidates
|
|
102046
102586
|
};
|
|
102047
102587
|
switch (f.kind) {
|
|
@@ -102079,7 +102619,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
102079
102619
|
meta
|
|
102080
102620
|
});
|
|
102081
102621
|
return;
|
|
102082
|
-
case "new":
|
|
102622
|
+
case "new": {
|
|
102623
|
+
if (f.reason === "clock-step") {
|
|
102624
|
+
trackerLog.debug("tracker: piece decided new — closed by a frame clock step", {
|
|
102625
|
+
tags: { deviceId },
|
|
102626
|
+
meta
|
|
102627
|
+
});
|
|
102628
|
+
return;
|
|
102629
|
+
}
|
|
102083
102630
|
if (f.reason === "unconfirmed") {
|
|
102084
102631
|
trackerLog.debug("tracker: piece decided new — never confirmed", {
|
|
102085
102632
|
tags: { deviceId },
|
|
@@ -102087,6 +102634,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
102087
102634
|
});
|
|
102088
102635
|
return;
|
|
102089
102636
|
}
|
|
102637
|
+
const rejectedPredecessors = confirmedPredecessorRejections(f.candidates);
|
|
102638
|
+
if (rejectedPredecessors.length > 0) {
|
|
102639
|
+
trackerLog.info("tracker: piece decided new — a confirmed predecessor was rejected", {
|
|
102640
|
+
tags: { deviceId },
|
|
102641
|
+
meta: {
|
|
102642
|
+
...decision,
|
|
102643
|
+
rejectedPredecessors
|
|
102644
|
+
}
|
|
102645
|
+
});
|
|
102646
|
+
return;
|
|
102647
|
+
}
|
|
102090
102648
|
newCount += 1;
|
|
102091
102649
|
if (!sampledTrackerFact(newCount, TRACKER_PIECE_LOG_SAMPLE_EVERY)) return;
|
|
102092
102650
|
trackerLog.info("tracker: piece decided new — no predecessor", {
|
|
@@ -102097,6 +102655,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
102097
102655
|
}
|
|
102098
102656
|
});
|
|
102099
102657
|
return;
|
|
102658
|
+
}
|
|
102100
102659
|
}
|
|
102101
102660
|
};
|
|
102102
102661
|
p.setTrackerObservers({
|
|
@@ -102117,6 +102676,39 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
102117
102676
|
});
|
|
102118
102677
|
},
|
|
102119
102678
|
link: logLink,
|
|
102679
|
+
clockStep: (f) => {
|
|
102680
|
+
clockStepCount += 1;
|
|
102681
|
+
if (!sampledTrackerFact(clockStepCount, TRACKER_CLOCK_STEP_LOG_SAMPLE_EVERY)) return;
|
|
102682
|
+
trackerLog.warn("tracker: frame clock stepped back — tracklets ended, clock rebased", {
|
|
102683
|
+
tags: { deviceId },
|
|
102684
|
+
meta: {
|
|
102685
|
+
source,
|
|
102686
|
+
t: f.t,
|
|
102687
|
+
lastFrameT: f.lastFrameT,
|
|
102688
|
+
behindMs: f.behindMs,
|
|
102689
|
+
ended: f.ended,
|
|
102690
|
+
clockStepCount,
|
|
102691
|
+
sampledEvery: TRACKER_CLOCK_STEP_LOG_SAMPLE_EVERY
|
|
102692
|
+
}
|
|
102693
|
+
});
|
|
102694
|
+
},
|
|
102695
|
+
outOfOrder: (f) => {
|
|
102696
|
+
outOfOrderCount += 1;
|
|
102697
|
+
if (!sampledTrackerFact(outOfOrderCount, TRACKER_OUT_OF_ORDER_LOG_SAMPLE_EVERY)) return;
|
|
102698
|
+
trackerLog.info("tracker: out-of-order frame dropped — older than the latest frame", {
|
|
102699
|
+
tags: { deviceId },
|
|
102700
|
+
meta: {
|
|
102701
|
+
source,
|
|
102702
|
+
t: f.t,
|
|
102703
|
+
lastFrameT: f.lastFrameT,
|
|
102704
|
+
behindMs: f.behindMs,
|
|
102705
|
+
detections: f.detections,
|
|
102706
|
+
pendingClockStep: f.pendingClockStep,
|
|
102707
|
+
outOfOrderCount,
|
|
102708
|
+
sampledEvery: TRACKER_OUT_OF_ORDER_LOG_SAMPLE_EVERY
|
|
102709
|
+
}
|
|
102710
|
+
});
|
|
102711
|
+
},
|
|
102120
102712
|
ended: (f) => {
|
|
102121
102713
|
const k = `${f.reason}:${String(f.confirmed)}`;
|
|
102122
102714
|
const n = (endedCounts.get(k) ?? 0) + 1;
|
|
@@ -102258,6 +102850,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
102258
102850
|
});
|
|
102259
102851
|
});
|
|
102260
102852
|
this.reportTrackerSummary(deviceId, key, processor, "frame");
|
|
102853
|
+
if (result.droppedOutOfOrder === true) return;
|
|
102261
102854
|
for (const r of processor.getLastLabelWriteRefusals()) {
|
|
102262
102855
|
const message = r.reason.kind === "macro-class" ? "label tier collapse refused" : "label write refused";
|
|
102263
102856
|
this.ctx.logger.info(message, {
|