@camstack/addon-post-analysis 1.2.666 → 1.2.668
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/CHANGELOG.md +18 -0
- package/dist/{clip-model-registry-Uc_C1x81.mjs → clip-model-registry-BYJ5lbPi.mjs} +922 -509
- package/dist/{clip-model-registry-BIhYJRFt.js → clip-model-registry-DTC5bkf8.js} +922 -509
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-FkeZoNXj.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DBEA3TPX.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DxtNkzmw.mjs +26 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CU7mDiJp.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-BBhgVoNl.mjs → hostInit-ChYqK-gn.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +460 -116
- package/dist/pipeline-analytics/index.mjs +460 -116
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CnYZAdvw.mjs +0 -26
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Br6eWzp_.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-DTC5bkf8.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");
|
|
@@ -59754,6 +59754,140 @@ async function promoteWithStill(input) {
|
|
|
59754
59754
|
});
|
|
59755
59755
|
}
|
|
59756
59756
|
//#endregion
|
|
59757
|
+
//#region src/pipeline-analytics/pipeline/key-frame-persist.ts
|
|
59758
|
+
/**
|
|
59759
|
+
* A box is written only when it can be USED — a degenerate rectangle, or one
|
|
59760
|
+
* measured on a degenerate frame, says nothing and would read back as a box at
|
|
59761
|
+
* the origin. Absent is the honest answer; the reader then pairs a snapshot,
|
|
59762
|
+
* exactly as it did before D510.
|
|
59763
|
+
*
|
|
59764
|
+
* Its result is spread into BOTH writes: the small companion is derived from
|
|
59765
|
+
* the very bytes of the native one, so it is the same scene at the same
|
|
59766
|
+
* instant and the box maps into it by ratio like any other scale of the frame.
|
|
59767
|
+
* A box on one row and not the other would make the summary's answer depend on
|
|
59768
|
+
* which row a reader happened to pick.
|
|
59769
|
+
*/
|
|
59770
|
+
function isStatableSubject(subject) {
|
|
59771
|
+
if (subject === void 0) return false;
|
|
59772
|
+
return subject.w > 0 && subject.h > 0 && subject.frameWidth > 0 && subject.frameHeight > 0;
|
|
59773
|
+
}
|
|
59774
|
+
/**
|
|
59775
|
+
* Read, derive and store one track's key frame + its web companion.
|
|
59776
|
+
*
|
|
59777
|
+
* PARCEL-OR-NOTHING: no parcel writes nothing and the caller's bounded retry
|
|
59778
|
+
* parks a later frame. The `keyFrame` write lands FIRST and its media id (D482)
|
|
59779
|
+
* is what the outcome carries — a `keyFrameSmall` failure afterwards must not
|
|
59780
|
+
* re-arm a retry that would re-shoot a key frame the rebuild can already read.
|
|
59781
|
+
*/
|
|
59782
|
+
async function persistTrackKeyFrame(deps, input) {
|
|
59783
|
+
const { deviceId, trackId, retry } = input;
|
|
59784
|
+
const drop = {
|
|
59785
|
+
tags: { deviceId },
|
|
59786
|
+
meta: {
|
|
59787
|
+
trackId,
|
|
59788
|
+
retry
|
|
59789
|
+
}
|
|
59790
|
+
};
|
|
59791
|
+
try {
|
|
59792
|
+
const parcel = await deps.fetchParkedKeyFrame(deviceId, trackId);
|
|
59793
|
+
if (parcel === null || parcel.jpeg.byteLength === 0) {
|
|
59794
|
+
deps.logger.error("key-frame parcel missing — nothing to write, retry parks a later frame", { ...drop });
|
|
59795
|
+
return { kind: "parcel-missing" };
|
|
59796
|
+
}
|
|
59797
|
+
const subjectBox = isStatableSubject(parcel.subject) ? { subjectBox: parcel.subject } : {};
|
|
59798
|
+
const keyFrameMediaId = await deps.media.putReplacing({
|
|
59799
|
+
deviceId,
|
|
59800
|
+
ownerKind: "track",
|
|
59801
|
+
ownerId: trackId,
|
|
59802
|
+
kind: "keyFrame",
|
|
59803
|
+
timestamp: parcel.timestamp,
|
|
59804
|
+
data: parcel.jpeg,
|
|
59805
|
+
...subjectBox
|
|
59806
|
+
});
|
|
59807
|
+
try {
|
|
59808
|
+
const keyFrameSmall = await deps.deriveSmall(parcel.jpeg);
|
|
59809
|
+
await deps.media.putReplacing({
|
|
59810
|
+
deviceId,
|
|
59811
|
+
ownerKind: "track",
|
|
59812
|
+
ownerId: trackId,
|
|
59813
|
+
kind: "keyFrameSmall",
|
|
59814
|
+
timestamp: parcel.timestamp,
|
|
59815
|
+
data: keyFrameSmall,
|
|
59816
|
+
...subjectBox
|
|
59817
|
+
});
|
|
59818
|
+
} catch (err) {
|
|
59819
|
+
deps.logger.warn("keyFrameSmall write failed — the native keyFrame is stored", {
|
|
59820
|
+
...drop,
|
|
59821
|
+
meta: {
|
|
59822
|
+
...drop.meta,
|
|
59823
|
+
nodeId: parcel.nodeId,
|
|
59824
|
+
error: require_clip_model_registry.errMsg(err)
|
|
59825
|
+
}
|
|
59826
|
+
});
|
|
59827
|
+
}
|
|
59828
|
+
deps.logger.debug("key frame written from the parked parcel", {
|
|
59829
|
+
...drop,
|
|
59830
|
+
meta: {
|
|
59831
|
+
...drop.meta,
|
|
59832
|
+
nodeId: parcel.nodeId,
|
|
59833
|
+
width: parcel.width,
|
|
59834
|
+
height: parcel.height,
|
|
59835
|
+
bytes: parcel.jpeg.byteLength,
|
|
59836
|
+
parkedTimestamp: parcel.timestamp
|
|
59837
|
+
}
|
|
59838
|
+
});
|
|
59839
|
+
return {
|
|
59840
|
+
kind: "landed",
|
|
59841
|
+
keyFrameMediaId
|
|
59842
|
+
};
|
|
59843
|
+
} catch (err) {
|
|
59844
|
+
const error = require_clip_model_registry.errMsg(err);
|
|
59845
|
+
deps.logger.warn("key-frame write failed", {
|
|
59846
|
+
...drop,
|
|
59847
|
+
meta: {
|
|
59848
|
+
...drop.meta,
|
|
59849
|
+
error
|
|
59850
|
+
}
|
|
59851
|
+
});
|
|
59852
|
+
return {
|
|
59853
|
+
kind: "error",
|
|
59854
|
+
error
|
|
59855
|
+
};
|
|
59856
|
+
}
|
|
59857
|
+
}
|
|
59858
|
+
/**
|
|
59859
|
+
* Write one parked frame under an OBJECT event (`object:<id>`), once.
|
|
59860
|
+
*
|
|
59861
|
+
* A package delivery has no track row owning a key frame, and the notification
|
|
59862
|
+
* ladder reads only `track:` and `object:` owners — so without this the
|
|
59863
|
+
* delivery ships text-only. `null` = no parcel to write (said at the caller).
|
|
59864
|
+
* `putReplacing` makes a repeat the same row, never a second one.
|
|
59865
|
+
*/
|
|
59866
|
+
async function persistObjectFrame(deps, input) {
|
|
59867
|
+
const { deviceId, trackId, objectId, timestamp } = input;
|
|
59868
|
+
const parcel = await deps.fetchParkedKeyFrame(deviceId, trackId);
|
|
59869
|
+
if (parcel === null || parcel.jpeg.byteLength === 0) {
|
|
59870
|
+
deps.logger.warn("object frame parcel missing — nothing to write", {
|
|
59871
|
+
tags: { deviceId },
|
|
59872
|
+
meta: {
|
|
59873
|
+
trackId,
|
|
59874
|
+
objectId
|
|
59875
|
+
}
|
|
59876
|
+
});
|
|
59877
|
+
return null;
|
|
59878
|
+
}
|
|
59879
|
+
const subjectBox = isStatableSubject(parcel.subject) ? { subjectBox: parcel.subject } : {};
|
|
59880
|
+
return deps.media.putReplacing({
|
|
59881
|
+
deviceId,
|
|
59882
|
+
ownerKind: "object",
|
|
59883
|
+
ownerId: objectId,
|
|
59884
|
+
kind: "keyFrame",
|
|
59885
|
+
timestamp,
|
|
59886
|
+
data: parcel.jpeg,
|
|
59887
|
+
...subjectBox
|
|
59888
|
+
});
|
|
59889
|
+
}
|
|
59890
|
+
//#endregion
|
|
59757
59891
|
//#region src/pipeline-analytics/pipeline/person-over-vehicle.ts
|
|
59758
59892
|
/**
|
|
59759
59893
|
* How much of the PERSON box must sit inside a vehicle box to be counted.
|
|
@@ -69135,10 +69269,18 @@ function applianceCycleOf(subject) {
|
|
|
69135
69269
|
//#region src/notification-center/appliance-cycle-runs.ts
|
|
69136
69270
|
var CYCLE_FACTS_WAIT_MS = 3e3;
|
|
69137
69271
|
var CYCLE_FACTS_POLL_MS = 250;
|
|
69138
|
-
/** A closed run belongs to this finish when it ended within this much
|
|
69272
|
+
/** A closed run belongs to this finish when it ended within this much AFTER it. */
|
|
69139
69273
|
var CYCLE_RUN_MATCH_MS = 10 * 6e4;
|
|
69274
|
+
/**
|
|
69275
|
+
* ... or up to this long BEFORE it: a run ends at the last real active instant
|
|
69276
|
+
* (the start of the pending stop), and the finish is announced one stop dwell
|
|
69277
|
+
* later - the longest a stop dwell is proposed is 20 minutes.
|
|
69278
|
+
*/
|
|
69279
|
+
var CYCLE_RUN_END_LAG_MS = 30 * 6e4;
|
|
69140
69280
|
function factsOfClosed(run, finishedAtMs) {
|
|
69141
|
-
if (run.endedAt === null
|
|
69281
|
+
if (run.endedAt === null) return NC_UNKNOWN_CYCLE_FACTS;
|
|
69282
|
+
const lag = finishedAtMs - run.endedAt;
|
|
69283
|
+
if (lag > CYCLE_RUN_END_LAG_MS || -lag > CYCLE_RUN_MATCH_MS) return NC_UNKNOWN_CYCLE_FACTS;
|
|
69142
69284
|
return {
|
|
69143
69285
|
durationMs: run.durationMs,
|
|
69144
69286
|
energyKwh: run.detail.energyKwh
|
|
@@ -71161,6 +71303,12 @@ Object.freeze({
|
|
|
71161
71303
|
"idleStopMinutes": 30
|
|
71162
71304
|
} } }
|
|
71163
71305
|
},
|
|
71306
|
+
"media-player": {
|
|
71307
|
+
"channelDown": { "steps": 1 },
|
|
71308
|
+
"channelUp": { "steps": 1 },
|
|
71309
|
+
"volumeDown": { "steps": 1 },
|
|
71310
|
+
"volumeUp": { "steps": 1 }
|
|
71311
|
+
},
|
|
71164
71312
|
"mqtt-broker": { "startEmbeddedBroker": {
|
|
71165
71313
|
"port": 1883,
|
|
71166
71314
|
"allowAnonymous": false
|
|
@@ -71404,6 +71552,7 @@ Object.freeze({
|
|
|
71404
71552
|
"device-export": [
|
|
71405
71553
|
"listExposedDevices",
|
|
71406
71554
|
"listExposedEntities",
|
|
71555
|
+
"listInstances",
|
|
71407
71556
|
"listSupportedDeviceKinds",
|
|
71408
71557
|
"listSupportedEntityKinds"
|
|
71409
71558
|
],
|
|
@@ -71655,6 +71804,15 @@ var WORD_LEVEL_FIELD = {
|
|
|
71655
71804
|
*/
|
|
71656
71805
|
var BOOLEAN_LEVEL_FIELD = { "running-state": "running" };
|
|
71657
71806
|
/**
|
|
71807
|
+
* The device-history series key of a level cap's boolean — `cap:<cap>.<field>`,
|
|
71808
|
+
* the shape of `capSeriesKey` (system builtin, not importable from an addon;
|
|
71809
|
+
* the wiring spec pins the literal). `null` for a cap with no boolean level.
|
|
71810
|
+
*/
|
|
71811
|
+
function levelSeriesKey(capName) {
|
|
71812
|
+
const field = BOOLEAN_LEVEL_FIELD[capName];
|
|
71813
|
+
return field === void 0 ? null : `cap:${capName}.${field}`;
|
|
71814
|
+
}
|
|
71815
|
+
/**
|
|
71658
71816
|
* Caps whose level is a WORD with NO change stamp: cap → field. `enum-sensor`
|
|
71659
71817
|
* carries `{ value, lastFetchedAt }`, and `lastFetchedAt` moves on every
|
|
71660
71818
|
* provider refresh, so it says nothing about when the word changed. Gated by
|
|
@@ -71681,6 +71839,7 @@ var WORD_LEVEL_INACTIVE = {
|
|
|
71681
71839
|
* post-analysis runner, D28).
|
|
71682
71840
|
*/
|
|
71683
71841
|
var ADVANCE_FIELD = require_clip_model_registry.CAP_ADVANCE_FIELDS;
|
|
71842
|
+
var DEFAULT_SEED_WAIT_MS = 2e3;
|
|
71684
71843
|
/** Default drop-summary window: one line per device per ten minutes, at most. */
|
|
71685
71844
|
var DEFAULT_DROP_SUMMARY_WINDOW_MS = 6e5;
|
|
71686
71845
|
var SensorTransitionGate = class {
|
|
@@ -71692,6 +71851,14 @@ var SensorTransitionGate = class {
|
|
|
71692
71851
|
lastStamps = /* @__PURE__ */ new Map();
|
|
71693
71852
|
/** Drops since the window opened, keyed by `${deviceId}:${capName}:${reason}`. */
|
|
71694
71853
|
drops = /* @__PURE__ */ new Map();
|
|
71854
|
+
/** Seed reads started, by key; a settled one stays so a seed is read once. */
|
|
71855
|
+
seeds = /* @__PURE__ */ new Map();
|
|
71856
|
+
seedsSettled = /* @__PURE__ */ new Set();
|
|
71857
|
+
/** Seeds whose wait ran out: decided as unseeded from then on. */
|
|
71858
|
+
seedsAbandoned = /* @__PURE__ */ new Set();
|
|
71859
|
+
readSeed;
|
|
71860
|
+
onSeedFailed;
|
|
71861
|
+
seedWaitMs;
|
|
71695
71862
|
windowStartedAt;
|
|
71696
71863
|
startedAtMs;
|
|
71697
71864
|
now;
|
|
@@ -71703,6 +71870,55 @@ var SensorTransitionGate = class {
|
|
|
71703
71870
|
this.freshnessMs = options.firstSightingFreshnessMs ?? require_clip_model_registry.DEFAULT_FIRST_SIGHTING_FRESHNESS_MS;
|
|
71704
71871
|
this.summaryWindowMs = options.summaryWindowMs ?? DEFAULT_DROP_SUMMARY_WINDOW_MS;
|
|
71705
71872
|
this.windowStartedAt = options.now();
|
|
71873
|
+
this.readSeed = options.readSeed;
|
|
71874
|
+
this.onSeedFailed = options.onSeedFailed;
|
|
71875
|
+
this.seedWaitMs = options.seedWaitMs ?? DEFAULT_SEED_WAIT_MS;
|
|
71876
|
+
}
|
|
71877
|
+
/**
|
|
71878
|
+
* {@link observe}, plus: a level cap's first boolean `false` whose persisted
|
|
71879
|
+
* prior is still being read waits for that read, bounded by `seedWaitMs`, and
|
|
71880
|
+
* is decided again. This is the only wait on the path, and it is on a read
|
|
71881
|
+
* that was started at the FIRST sighting of the device (a null at boot, long
|
|
71882
|
+
* before the boolean) — never one begun by the event itself unless the
|
|
71883
|
+
* boolean really is the first thing seen. A read that fails or times out
|
|
71884
|
+
* decides as unseeded, i.e. exactly as before the seed existed (D49).
|
|
71885
|
+
*/
|
|
71886
|
+
async observeSettled(deviceId, capName, slice) {
|
|
71887
|
+
const first = this.observe(deviceId, capName, slice);
|
|
71888
|
+
if (first.changed || first.reason !== "seed-pending") return first;
|
|
71889
|
+
const key = `${deviceId}:${capName}`;
|
|
71890
|
+
const pending = this.seeds.get(key);
|
|
71891
|
+
if (pending !== void 0) {
|
|
71892
|
+
let timer;
|
|
71893
|
+
const timeout = new Promise((resolve) => {
|
|
71894
|
+
timer = setTimeout(resolve, this.seedWaitMs);
|
|
71895
|
+
});
|
|
71896
|
+
await Promise.race([pending, timeout]);
|
|
71897
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
71898
|
+
if (!this.seedsSettled.has(key)) this.seedsAbandoned.add(key);
|
|
71899
|
+
}
|
|
71900
|
+
return this.observe(deviceId, capName, slice);
|
|
71901
|
+
}
|
|
71902
|
+
/** Start the one seed read of a level cap's key; never awaited here. */
|
|
71903
|
+
ensureSeed(deviceId, capName) {
|
|
71904
|
+
const read = this.readSeed;
|
|
71905
|
+
if (read === void 0) return;
|
|
71906
|
+
const key = `${deviceId}:${capName}`;
|
|
71907
|
+
if (this.seeds.has(key)) return;
|
|
71908
|
+
const settle = () => {
|
|
71909
|
+
this.seedsSettled.add(key);
|
|
71910
|
+
};
|
|
71911
|
+
const started = read(deviceId, capName).then((seed) => {
|
|
71912
|
+
if (seed !== null && !this.lastValues.has(key)) this.lastValues.set(key, seed.value);
|
|
71913
|
+
settle();
|
|
71914
|
+
}, (error) => {
|
|
71915
|
+
this.onSeedFailed?.(deviceId, capName, error);
|
|
71916
|
+
settle();
|
|
71917
|
+
});
|
|
71918
|
+
this.seeds.set(key, started);
|
|
71919
|
+
}
|
|
71920
|
+
seedPending(key) {
|
|
71921
|
+
return this.seeds.has(key) && !this.seedsSettled.has(key) && !this.seedsAbandoned.has(key);
|
|
71706
71922
|
}
|
|
71707
71923
|
/**
|
|
71708
71924
|
* Feed one `DeviceStateChanged` slice for a cap `EVENT_KIND_BY_CAP` maps.
|
|
@@ -71714,6 +71930,7 @@ var SensorTransitionGate = class {
|
|
|
71714
71930
|
* a boolean in either direction, or a word-level cap's word changing.
|
|
71715
71931
|
*/
|
|
71716
71932
|
observe(deviceId, capName, slice) {
|
|
71933
|
+
if (BOOLEAN_LEVEL_FIELD[capName] !== void 0) this.ensureSeed(deviceId, capName);
|
|
71717
71934
|
if (slice["lastChangedAt"] === 0) return this.drop(deviceId, capName, "cold-slice");
|
|
71718
71935
|
const unstampedField = UNSTAMPED_WORD_FIELD[capName];
|
|
71719
71936
|
if (unstampedField !== void 0) return this.observeUnstampedWord(deviceId, capName, slice, unstampedField);
|
|
@@ -71769,6 +71986,10 @@ var SensorTransitionGate = class {
|
|
|
71769
71986
|
if (typeof raw !== "boolean") return this.drop(deviceId, capName, "non-boolean-value");
|
|
71770
71987
|
const key = `${deviceId}:${capName}`;
|
|
71771
71988
|
const prior = this.lastValues.get(key);
|
|
71989
|
+
if (prior === void 0 && !raw && this.seedPending(key)) return {
|
|
71990
|
+
changed: false,
|
|
71991
|
+
reason: "seed-pending"
|
|
71992
|
+
};
|
|
71772
71993
|
this.lastValues.set(key, raw);
|
|
71773
71994
|
if (prior === void 0) {
|
|
71774
71995
|
if (!raw) return this.drop(deviceId, capName, "baseline-seeded-inactive");
|
|
@@ -85607,6 +85828,15 @@ function isLinkedStillId(id) {
|
|
|
85607
85828
|
var NC_LINKED_WAKE_CAPTURE_TIMEOUT_MS = 15e3;
|
|
85608
85829
|
var NC_LINKED_WAKE_POLL_MS = 1e3;
|
|
85609
85830
|
var NC_LINKED_WAKE_MIN_GAP_MS = 6e4;
|
|
85831
|
+
/**
|
|
85832
|
+
* D60 — a tile the freshness read calls stale is RE-POLLED, unforced, until the
|
|
85833
|
+
* wrapper hands back a frame from the event or this bound is spent. The wrapper
|
|
85834
|
+
* settles a soft miss with its old cached hit; one more ask, a moment later,
|
|
85835
|
+
* usually finds the refreshed one. The bound sits inside the dispatcher's still
|
|
85836
|
+
* deadline (`NC_STILL_WAIT_MS`, 3 s): the row waits for it through `pendingUntil`.
|
|
85837
|
+
*/
|
|
85838
|
+
var NC_LINKED_STALE_POLL_MS = 500;
|
|
85839
|
+
var NC_LINKED_STALE_MAX_POLLS = 5;
|
|
85610
85840
|
/** Per camera — the marker's own bound (`SENSOR_MARKER_SNAPSHOT_TIMEOUT_MS`). */
|
|
85611
85841
|
var SENSOR_LINKED_CAPTURE_TIMEOUT_MS = 2500;
|
|
85612
85842
|
/** The capture-free wrapper-state read, per camera. It runs IN PARALLEL with
|
|
@@ -85616,6 +85846,8 @@ var SENSOR_LINKED_CAPTURE_TIMEOUT_MS = 2500;
|
|
|
85616
85846
|
* photograph). A read that misses the bound or fails is UNKNOWN and keeps the
|
|
85617
85847
|
* tile (D49). */
|
|
85618
85848
|
var SENSOR_LINKED_STATE_READ_TIMEOUT_MS = SENSOR_LINKED_CAPTURE_TIMEOUT_MS;
|
|
85849
|
+
/** The battery-slice read behind the unknown-state wake: cheap, and bounded. */
|
|
85850
|
+
var SENSOR_LINKED_BATTERY_READ_TIMEOUT_MS = 1e3;
|
|
85619
85851
|
/** The one cache-only freshness read for the set — one hop, after the
|
|
85620
85852
|
* captures. A read that misses it keeps every tile and says `freshness
|
|
85621
85853
|
* unverified` (D49). */
|
|
@@ -85721,13 +85953,13 @@ var NcLinkedStillShelf = class {
|
|
|
85721
85953
|
startedAt,
|
|
85722
85954
|
options
|
|
85723
85955
|
};
|
|
85724
|
-
let
|
|
85956
|
+
let judged;
|
|
85725
85957
|
try {
|
|
85726
|
-
results = await Promise.all(stills.cameraIds.map((id) => this.captureOne(id, ctx)));
|
|
85958
|
+
const results = await Promise.all(stills.cameraIds.map((id) => this.captureOne(id, ctx)));
|
|
85959
|
+
judged = await this.repollStale(ctx, atMs, await this.judgeFreshness(stills.sensorId, results, atMs));
|
|
85727
85960
|
} finally {
|
|
85728
85961
|
this.pendingWakes.delete(setId);
|
|
85729
85962
|
}
|
|
85730
|
-
const judged = await this.judgeFreshness(stills.sensorId, results, atMs);
|
|
85731
85963
|
for (const r of judged) this.logTile(stills.sensorId, r);
|
|
85732
85964
|
const landed = judged.filter(isLandedTile);
|
|
85733
85965
|
const composed = await this.compose(stills.sensorId, landed, atMs);
|
|
@@ -85774,6 +86006,16 @@ var NcLinkedStillShelf = class {
|
|
|
85774
86006
|
if (verdict.reason === "waking" && ctx.options.wakeSleeping) return this.awaitWaking(cameraId, ctx);
|
|
85775
86007
|
if (verdict.reason === "unreachable" && ctx.options.wakeSleeping) return excluded("wake-skipped:unreachable");
|
|
85776
86008
|
if (verdict.reason !== null) return excluded(verdict.reason);
|
|
86009
|
+
if (verdict.unknown !== null && capture.outcome === "empty" && ctx.options.wakeSleeping && await this.isBatteryCamera(cameraId)) {
|
|
86010
|
+
this.deps.logger.info("linked camera state unknown and capture empty — waking as a battery camera", {
|
|
86011
|
+
tags: { deviceId: cameraId },
|
|
86012
|
+
meta: {
|
|
86013
|
+
sensorId: ctx.sensorId,
|
|
86014
|
+
stateUnknown: verdict.unknown
|
|
86015
|
+
}
|
|
86016
|
+
});
|
|
86017
|
+
return this.wakeOne(cameraId, ctx);
|
|
86018
|
+
}
|
|
85777
86019
|
return {
|
|
85778
86020
|
cameraId,
|
|
85779
86021
|
...capture,
|
|
@@ -85781,6 +86023,20 @@ var NcLinkedStillShelf = class {
|
|
|
85781
86023
|
tookMs: took()
|
|
85782
86024
|
};
|
|
85783
86025
|
}
|
|
86026
|
+
/** Only a camera KNOWN to be a battery camera is woken on an unknown state. */
|
|
86027
|
+
async isBatteryCamera(cameraId) {
|
|
86028
|
+
const read = this.deps.isBatteryCamera;
|
|
86029
|
+
if (read === void 0) return false;
|
|
86030
|
+
try {
|
|
86031
|
+
return await withTimeout$2(read(cameraId), SENSOR_LINKED_BATTERY_READ_TIMEOUT_MS, "battery read") === true;
|
|
86032
|
+
} catch (err) {
|
|
86033
|
+
this.deps.logger.warn("linked camera battery read failed — not woken on an unknown state", {
|
|
86034
|
+
tags: { deviceId: cameraId },
|
|
86035
|
+
meta: { error: String(err) }
|
|
86036
|
+
});
|
|
86037
|
+
return false;
|
|
86038
|
+
}
|
|
86039
|
+
}
|
|
85784
86040
|
/** D998: one forced capture, by the rule's leave, at most once per gap per camera. */
|
|
85785
86041
|
async wakeOne(cameraId, ctx) {
|
|
85786
86042
|
const took = () => this.deps.now() - ctx.startedAt;
|
|
@@ -85842,8 +86098,46 @@ var NcLinkedStillShelf = class {
|
|
|
85842
86098
|
};
|
|
85843
86099
|
}
|
|
85844
86100
|
markPending(ctx) {
|
|
85845
|
-
|
|
85846
|
-
|
|
86101
|
+
this.markPendingUntil(ctx.setId, ctx.startedAt + NC_LINKED_WAKE_CAPTURE_TIMEOUT_MS + NC_LINKED_WAKE_POLL_MS);
|
|
86102
|
+
}
|
|
86103
|
+
markPendingUntil(setId, until) {
|
|
86104
|
+
this.pendingWakes.set(setId, Math.max(this.pendingWakes.get(setId) ?? 0, until));
|
|
86105
|
+
}
|
|
86106
|
+
/**
|
|
86107
|
+
* D60: a `stale` tile asks again (unforced — a poll never wakes) until its
|
|
86108
|
+
* frame is from the event or the bound is spent; the row's still-wait is held
|
|
86109
|
+
* open meanwhile. A camera that never freshens stays `stale`, said on its line.
|
|
86110
|
+
*/
|
|
86111
|
+
async repollStale(ctx, atMs, judged) {
|
|
86112
|
+
let current = judged;
|
|
86113
|
+
if (!current.some((r) => r.outcome === "stale")) return current;
|
|
86114
|
+
this.markPendingUntil(ctx.setId, this.deps.now() + NC_LINKED_STALE_POLL_MS * 6);
|
|
86115
|
+
for (let i = 0; i < NC_LINKED_STALE_MAX_POLLS; i++) {
|
|
86116
|
+
const waiting = current.filter((r) => r.outcome === "stale").map((r) => r.cameraId);
|
|
86117
|
+
if (waiting.length === 0) break;
|
|
86118
|
+
await new Promise((resolve) => setTimeout(resolve, NC_LINKED_STALE_POLL_MS));
|
|
86119
|
+
const landedAgain = (await Promise.all(waiting.map(async (cameraId) => {
|
|
86120
|
+
const capture = await this.captureStill(cameraId, false, SENSOR_LINKED_CAPTURE_TIMEOUT_MS);
|
|
86121
|
+
return capture.outcome === "landed" && capture.jpeg !== void 0 ? {
|
|
86122
|
+
cameraId,
|
|
86123
|
+
outcome: "landed",
|
|
86124
|
+
jpeg: capture.jpeg,
|
|
86125
|
+
tookMs: this.deps.now() - ctx.startedAt
|
|
86126
|
+
} : null;
|
|
86127
|
+
}))).filter((r) => r !== null);
|
|
86128
|
+
if (landedAgain.length === 0) continue;
|
|
86129
|
+
const rejudged = await this.judgeFreshness(ctx.sensorId, landedAgain, atMs);
|
|
86130
|
+
const fresh = new Map(rejudged.filter((r) => r.outcome === "landed").map((r) => [r.cameraId, r]));
|
|
86131
|
+
current = current.map((r) => fresh.get(r.cameraId) ?? r);
|
|
86132
|
+
for (const cameraId of fresh.keys()) this.deps.logger.info("linked camera frame was stale — a re-poll brought one from the event", {
|
|
86133
|
+
tags: { deviceId: cameraId },
|
|
86134
|
+
meta: {
|
|
86135
|
+
sensorId: ctx.sensorId,
|
|
86136
|
+
polls: i + 1
|
|
86137
|
+
}
|
|
86138
|
+
});
|
|
86139
|
+
}
|
|
86140
|
+
return current;
|
|
85847
86141
|
}
|
|
85848
86142
|
/** A wake that brought nothing back names itself, not the generic `empty`. */
|
|
85849
86143
|
asWakeResult(capture) {
|
|
@@ -85912,9 +86206,17 @@ var NcLinkedStillShelf = class {
|
|
|
85912
86206
|
unknown: null
|
|
85913
86207
|
};
|
|
85914
86208
|
} catch (err) {
|
|
86209
|
+
const timedOut = err instanceof LinkedReadTimeout;
|
|
86210
|
+
this.deps.logger.warn("linked camera snapshot state read failed — state unknown", {
|
|
86211
|
+
tags: { deviceId: cameraId },
|
|
86212
|
+
meta: {
|
|
86213
|
+
error: String(err),
|
|
86214
|
+
timedOut
|
|
86215
|
+
}
|
|
86216
|
+
});
|
|
85915
86217
|
return {
|
|
85916
86218
|
reason: null,
|
|
85917
|
-
unknown:
|
|
86219
|
+
unknown: timedOut ? "timeout" : "error"
|
|
85918
86220
|
};
|
|
85919
86221
|
}
|
|
85920
86222
|
}
|
|
@@ -107556,6 +107858,7 @@ var NotificationCenter = class {
|
|
|
107556
107858
|
this.linkedStills = getSnapshot !== void 0 ? new NcLinkedStillShelf({
|
|
107557
107859
|
getSnapshot,
|
|
107558
107860
|
...deps.getSnapshotState !== void 0 ? { getSnapshotState: deps.getSnapshotState } : {},
|
|
107861
|
+
...deps.isBatteryCamera !== void 0 ? { isBatteryCamera: deps.isBatteryCamera } : {},
|
|
107559
107862
|
...deps.getFrameCapturedAt !== void 0 ? { getFrameCapturedAt: deps.getFrameCapturedAt } : {},
|
|
107560
107863
|
isOnline: (id) => this.deviceDirectory.get(id)?.online,
|
|
107561
107864
|
logger: this.logger.child("linked-stills"),
|
|
@@ -110327,6 +110630,7 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
|
|
|
110327
110630
|
now: () => Date.now()
|
|
110328
110631
|
}),
|
|
110329
110632
|
...buildLinkedSnapshotReads(api.snapshot),
|
|
110633
|
+
isBatteryCamera: async (deviceId) => await api.battery.getStatus.query({ deviceId }) !== null,
|
|
110330
110634
|
identityIdsByName: async () => {
|
|
110331
110635
|
const byName = /* @__PURE__ */ new Map();
|
|
110332
110636
|
const identities = await stores.identityStore.listIdentities();
|
|
@@ -114524,6 +114828,12 @@ function deliveredIdempotencyKey(entryId) {
|
|
|
114524
114828
|
function pickedUpIdempotencyKey(entryId) {
|
|
114525
114829
|
return `pa-pkg-${entryId}-pickedup`;
|
|
114526
114830
|
}
|
|
114831
|
+
/**
|
|
114832
|
+
* How long a delivery waits for its still to be written before it ships
|
|
114833
|
+
* without one. Under the notification still poll (`NC_STILL_WAIT_MS`, 3 s) so
|
|
114834
|
+
* a slow store never holds the alert longer than the dispatcher would.
|
|
114835
|
+
*/
|
|
114836
|
+
var PACKAGE_FRAME_PERSIST_BOUND_MS = 2e3;
|
|
114527
114837
|
var PackageDropDetector = class {
|
|
114528
114838
|
deps;
|
|
114529
114839
|
/** Per-device mirror of the last LOGGED value of the `packageDropEnabled`
|
|
@@ -114537,6 +114847,62 @@ var PackageDropDetector = class {
|
|
|
114537
114847
|
* LLM gate (D931). `null` means the delivery is dropped (no, or no yes).
|
|
114538
114848
|
* An empty object means the rule did not ask. A verdict is spread onto the row.
|
|
114539
114849
|
*/
|
|
114850
|
+
/**
|
|
114851
|
+
* The delivery's still, written once under `object:<rowId>` when the entry
|
|
114852
|
+
* had no key frame. Never throws and never waits past
|
|
114853
|
+
* {@link PACKAGE_FRAME_PERSIST_BOUND_MS}: a delivery without a picture still
|
|
114854
|
+
* ships, and says why (D391).
|
|
114855
|
+
*/
|
|
114856
|
+
async persistDeliveryStill(entry, rowId, timestamp) {
|
|
114857
|
+
const persist = this.deps.persistDeliveryFrame;
|
|
114858
|
+
if (persist === void 0) return void 0;
|
|
114859
|
+
const tags = { deviceId: entry.deviceId };
|
|
114860
|
+
const meta = {
|
|
114861
|
+
entryId: entry.id,
|
|
114862
|
+
rowId
|
|
114863
|
+
};
|
|
114864
|
+
let timer;
|
|
114865
|
+
try {
|
|
114866
|
+
const bound = new Promise((resolve) => {
|
|
114867
|
+
timer = setTimeout(() => resolve("timeout"), PACKAGE_FRAME_PERSIST_BOUND_MS);
|
|
114868
|
+
});
|
|
114869
|
+
const written = await Promise.race([persist({
|
|
114870
|
+
deviceId: entry.deviceId,
|
|
114871
|
+
trackId: entry.sourceTrackId ?? entry.id,
|
|
114872
|
+
rowId,
|
|
114873
|
+
timestamp
|
|
114874
|
+
}), bound]);
|
|
114875
|
+
if (written === "timeout") {
|
|
114876
|
+
this.deps.logger.warn("package delivery still not written in time — shipping without it", {
|
|
114877
|
+
tags,
|
|
114878
|
+
meta: {
|
|
114879
|
+
...meta,
|
|
114880
|
+
boundMs: PACKAGE_FRAME_PERSIST_BOUND_MS
|
|
114881
|
+
}
|
|
114882
|
+
});
|
|
114883
|
+
return;
|
|
114884
|
+
}
|
|
114885
|
+
if (written === null) {
|
|
114886
|
+
this.deps.logger.warn("package delivery has no frame to write — shipping without a still", {
|
|
114887
|
+
tags,
|
|
114888
|
+
meta
|
|
114889
|
+
});
|
|
114890
|
+
return;
|
|
114891
|
+
}
|
|
114892
|
+
return written;
|
|
114893
|
+
} catch (err) {
|
|
114894
|
+
this.deps.logger.warn("package delivery still write failed — shipping without it", {
|
|
114895
|
+
tags,
|
|
114896
|
+
meta: {
|
|
114897
|
+
...meta,
|
|
114898
|
+
error: require_clip_model_registry.errMsg(err)
|
|
114899
|
+
}
|
|
114900
|
+
});
|
|
114901
|
+
return;
|
|
114902
|
+
} finally {
|
|
114903
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
114904
|
+
}
|
|
114905
|
+
}
|
|
114540
114906
|
async llmFields(entry, rules, hitZones, tags) {
|
|
114541
114907
|
if (!rules.some((rule) => rule.confirmWithLlm === true && rule.zoneIds.some((id) => hitZones.includes(id)))) return {};
|
|
114542
114908
|
const confirm = this.deps.confirmPackage;
|
|
@@ -114798,6 +115164,7 @@ var PackageDropDetector = class {
|
|
|
114798
115164
|
});
|
|
114799
115165
|
return;
|
|
114800
115166
|
}
|
|
115167
|
+
const keyFrameMediaId = entry.keyFrameMediaId ?? await this.persistDeliveryStill(entry, rowId, rowTimestamp);
|
|
114801
115168
|
const ev = {
|
|
114802
115169
|
...draft,
|
|
114803
115170
|
id: rowId
|
|
@@ -114809,7 +115176,7 @@ var PackageDropDetector = class {
|
|
|
114809
115176
|
eventId: String(rowId),
|
|
114810
115177
|
className: entry.className,
|
|
114811
115178
|
zoneIds: hitZones,
|
|
114812
|
-
...
|
|
115179
|
+
...keyFrameMediaId !== void 0 ? { keyFrameMediaId } : {},
|
|
114813
115180
|
bbox: {
|
|
114814
115181
|
x: entry.bbox.x,
|
|
114815
115182
|
y: entry.bbox.y,
|
|
@@ -118372,6 +118739,7 @@ var BuildStores = class extends BuildStoresBase {
|
|
|
118372
118739
|
resolveSettings: (deviceId) => this.resolveDevicePackageDropSettings(deviceId),
|
|
118373
118740
|
resolveZoneMembershipMinOverlap: async (deviceId) => (await this.resolveDeviceTrackingSettings(deviceId)).zoneMembershipMinOverlap,
|
|
118374
118741
|
confirmPackage: (input) => this.deps.confirmPackage(input),
|
|
118742
|
+
persistDeliveryFrame: (request) => this.deps.persistPackageDeliveryFrame(request),
|
|
118375
118743
|
onLlmSilence: (edge) => {
|
|
118376
118744
|
const deviceId = edge.phase === "silent" ? edge.note.deviceId : edge.deviceId;
|
|
118377
118745
|
this.ctx.eventBus.emit({
|
|
@@ -120493,108 +120861,6 @@ async function ensureTrackHierarchyMedia(deps, input) {
|
|
|
120493
120861
|
await Promise.all(tasks);
|
|
120494
120862
|
}
|
|
120495
120863
|
//#endregion
|
|
120496
|
-
//#region src/pipeline-analytics/pipeline/key-frame-persist.ts
|
|
120497
|
-
/**
|
|
120498
|
-
* A box is written only when it can be USED — a degenerate rectangle, or one
|
|
120499
|
-
* measured on a degenerate frame, says nothing and would read back as a box at
|
|
120500
|
-
* the origin. Absent is the honest answer; the reader then pairs a snapshot,
|
|
120501
|
-
* exactly as it did before D510.
|
|
120502
|
-
*
|
|
120503
|
-
* Its result is spread into BOTH writes: the small companion is derived from
|
|
120504
|
-
* the very bytes of the native one, so it is the same scene at the same
|
|
120505
|
-
* instant and the box maps into it by ratio like any other scale of the frame.
|
|
120506
|
-
* A box on one row and not the other would make the summary's answer depend on
|
|
120507
|
-
* which row a reader happened to pick.
|
|
120508
|
-
*/
|
|
120509
|
-
function isStatableSubject(subject) {
|
|
120510
|
-
if (subject === void 0) return false;
|
|
120511
|
-
return subject.w > 0 && subject.h > 0 && subject.frameWidth > 0 && subject.frameHeight > 0;
|
|
120512
|
-
}
|
|
120513
|
-
/**
|
|
120514
|
-
* Read, derive and store one track's key frame + its web companion.
|
|
120515
|
-
*
|
|
120516
|
-
* PARCEL-OR-NOTHING: no parcel writes nothing and the caller's bounded retry
|
|
120517
|
-
* parks a later frame. The `keyFrame` write lands FIRST and its media id (D482)
|
|
120518
|
-
* is what the outcome carries — a `keyFrameSmall` failure afterwards must not
|
|
120519
|
-
* re-arm a retry that would re-shoot a key frame the rebuild can already read.
|
|
120520
|
-
*/
|
|
120521
|
-
async function persistTrackKeyFrame(deps, input) {
|
|
120522
|
-
const { deviceId, trackId, retry } = input;
|
|
120523
|
-
const drop = {
|
|
120524
|
-
tags: { deviceId },
|
|
120525
|
-
meta: {
|
|
120526
|
-
trackId,
|
|
120527
|
-
retry
|
|
120528
|
-
}
|
|
120529
|
-
};
|
|
120530
|
-
try {
|
|
120531
|
-
const parcel = await deps.fetchParkedKeyFrame(deviceId, trackId);
|
|
120532
|
-
if (parcel === null || parcel.jpeg.byteLength === 0) {
|
|
120533
|
-
deps.logger.error("key-frame parcel missing — nothing to write, retry parks a later frame", { ...drop });
|
|
120534
|
-
return { kind: "parcel-missing" };
|
|
120535
|
-
}
|
|
120536
|
-
const subjectBox = isStatableSubject(parcel.subject) ? { subjectBox: parcel.subject } : {};
|
|
120537
|
-
const keyFrameMediaId = await deps.media.putReplacing({
|
|
120538
|
-
deviceId,
|
|
120539
|
-
ownerKind: "track",
|
|
120540
|
-
ownerId: trackId,
|
|
120541
|
-
kind: "keyFrame",
|
|
120542
|
-
timestamp: parcel.timestamp,
|
|
120543
|
-
data: parcel.jpeg,
|
|
120544
|
-
...subjectBox
|
|
120545
|
-
});
|
|
120546
|
-
try {
|
|
120547
|
-
const keyFrameSmall = await deps.deriveSmall(parcel.jpeg);
|
|
120548
|
-
await deps.media.putReplacing({
|
|
120549
|
-
deviceId,
|
|
120550
|
-
ownerKind: "track",
|
|
120551
|
-
ownerId: trackId,
|
|
120552
|
-
kind: "keyFrameSmall",
|
|
120553
|
-
timestamp: parcel.timestamp,
|
|
120554
|
-
data: keyFrameSmall,
|
|
120555
|
-
...subjectBox
|
|
120556
|
-
});
|
|
120557
|
-
} catch (err) {
|
|
120558
|
-
deps.logger.warn("keyFrameSmall write failed — the native keyFrame is stored", {
|
|
120559
|
-
...drop,
|
|
120560
|
-
meta: {
|
|
120561
|
-
...drop.meta,
|
|
120562
|
-
nodeId: parcel.nodeId,
|
|
120563
|
-
error: require_clip_model_registry.errMsg(err)
|
|
120564
|
-
}
|
|
120565
|
-
});
|
|
120566
|
-
}
|
|
120567
|
-
deps.logger.debug("key frame written from the parked parcel", {
|
|
120568
|
-
...drop,
|
|
120569
|
-
meta: {
|
|
120570
|
-
...drop.meta,
|
|
120571
|
-
nodeId: parcel.nodeId,
|
|
120572
|
-
width: parcel.width,
|
|
120573
|
-
height: parcel.height,
|
|
120574
|
-
bytes: parcel.jpeg.byteLength,
|
|
120575
|
-
parkedTimestamp: parcel.timestamp
|
|
120576
|
-
}
|
|
120577
|
-
});
|
|
120578
|
-
return {
|
|
120579
|
-
kind: "landed",
|
|
120580
|
-
keyFrameMediaId
|
|
120581
|
-
};
|
|
120582
|
-
} catch (err) {
|
|
120583
|
-
const error = require_clip_model_registry.errMsg(err);
|
|
120584
|
-
deps.logger.warn("key-frame write failed", {
|
|
120585
|
-
...drop,
|
|
120586
|
-
meta: {
|
|
120587
|
-
...drop.meta,
|
|
120588
|
-
error
|
|
120589
|
-
}
|
|
120590
|
-
});
|
|
120591
|
-
return {
|
|
120592
|
-
kind: "error",
|
|
120593
|
-
error
|
|
120594
|
-
};
|
|
120595
|
-
}
|
|
120596
|
-
}
|
|
120597
|
-
//#endregion
|
|
120598
120864
|
//#region src/pipeline-analytics/pipeline/key-frame-retry.ts
|
|
120599
120865
|
/** Retry-path attempts allowed per track. The operator's number. */
|
|
120600
120866
|
var KEY_FRAME_MAX_RETRIES = 3;
|
|
@@ -122178,6 +122444,8 @@ var DEFAULT_ASSIST_MIN_SCORE = .3;
|
|
|
122178
122444
|
* cannot page the whole table into memory to build a manifest.
|
|
122179
122445
|
*/
|
|
122180
122446
|
var RETRAIN_EXPORT_MAX = 2e4;
|
|
122447
|
+
/** How far back a restart looks for a level cap's last persisted boolean. */
|
|
122448
|
+
var LEVEL_SEED_WINDOW_MS = 48 * 36e5;
|
|
122181
122449
|
/**
|
|
122182
122450
|
* Pipeline Analytics addon — subscribes to `PipelineInferenceResult` +
|
|
122183
122451
|
* `PipelineAudioInferenceResult` on the bus, refines raw detections
|
|
@@ -122453,7 +122721,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
122453
122721
|
*/
|
|
122454
122722
|
sensorTransitions = new SensorTransitionGate({
|
|
122455
122723
|
startedAtMs: Date.now(),
|
|
122456
|
-
now: () => Date.now()
|
|
122724
|
+
now: () => Date.now(),
|
|
122725
|
+
readSeed: (deviceId, capName) => this.readPersistedLevel(deviceId, capName),
|
|
122726
|
+
onSeedFailed: (deviceId, capName, error) => this.ctx.logger.warn("level seed read failed — gate starts unseeded", {
|
|
122727
|
+
tags: { deviceId },
|
|
122728
|
+
meta: {
|
|
122729
|
+
capName,
|
|
122730
|
+
error: require_clip_model_registry.errMsg(error)
|
|
122731
|
+
}
|
|
122732
|
+
})
|
|
122457
122733
|
});
|
|
122458
122734
|
/** `camera-index-unknown` drops, summarised per window — see the file. */
|
|
122459
122735
|
cameraIndexUnknownDrops = new CameraIndexUnknownDrops({ nowMs: Date.now() });
|
|
@@ -124265,6 +124541,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
124265
124541
|
this.packageDropDetector = value;
|
|
124266
124542
|
},
|
|
124267
124543
|
confirmPackage: (input) => this.confirmPackageDelivery(input),
|
|
124544
|
+
persistPackageDeliveryFrame: (request) => this.persistPackageDeliveryFrame(request),
|
|
124268
124545
|
stationaryEvidence: () => this.stationaryEvidence,
|
|
124269
124546
|
setStationaryEvidence: (value) => {
|
|
124270
124547
|
this.stationaryEvidence = value;
|
|
@@ -128420,6 +128697,44 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
128420
128697
|
via: "frame"
|
|
128421
128698
|
});
|
|
128422
128699
|
}
|
|
128700
|
+
/**
|
|
128701
|
+
* #383: a delivery whose entry had no key frame writes the source track's
|
|
128702
|
+
* parked frame under the object row it just created — the one owner the
|
|
128703
|
+
* notification ladder reads for a package event.
|
|
128704
|
+
*/
|
|
128705
|
+
async persistPackageDeliveryFrame(request) {
|
|
128706
|
+
const mediaStore = this.mediaStore;
|
|
128707
|
+
if (mediaStore === null) return null;
|
|
128708
|
+
return persistObjectFrame({
|
|
128709
|
+
fetchParkedKeyFrame: async (deviceId, trackId) => {
|
|
128710
|
+
const parcel = await this.mediaCapture.fetchParkedParcel(trackId, "keyFrame");
|
|
128711
|
+
if (parcel === null || parcel.info.deviceId !== deviceId) return null;
|
|
128712
|
+
const subject = parcel.info.subject;
|
|
128713
|
+
return {
|
|
128714
|
+
jpeg: parcel.jpeg,
|
|
128715
|
+
width: parcel.width,
|
|
128716
|
+
height: parcel.height,
|
|
128717
|
+
timestamp: parcel.timestamp,
|
|
128718
|
+
nodeId: parcel.info.nodeId,
|
|
128719
|
+
subject: {
|
|
128720
|
+
x: subject.bbox.x,
|
|
128721
|
+
y: subject.bbox.y,
|
|
128722
|
+
w: subject.bbox.w,
|
|
128723
|
+
h: subject.bbox.h,
|
|
128724
|
+
frameWidth: subject.frameWidth,
|
|
128725
|
+
frameHeight: subject.frameHeight
|
|
128726
|
+
}
|
|
128727
|
+
};
|
|
128728
|
+
},
|
|
128729
|
+
media: mediaStore,
|
|
128730
|
+
logger: this.ctx.logger
|
|
128731
|
+
}, {
|
|
128732
|
+
deviceId: request.deviceId,
|
|
128733
|
+
trackId: request.trackId,
|
|
128734
|
+
objectId: String(request.rowId),
|
|
128735
|
+
timestamp: request.timestamp
|
|
128736
|
+
});
|
|
128737
|
+
}
|
|
128423
128738
|
async confirmPackageDelivery(input) {
|
|
128424
128739
|
const crops = this.packageScene?.cropsFor(input.deviceId);
|
|
128425
128740
|
return confirmPackageWithLlm({
|
|
@@ -129421,7 +129736,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
129421
129736
|
const store = this.sensorEventStore;
|
|
129422
129737
|
const cache = this.linkedCamerasCache;
|
|
129423
129738
|
if (store === null || cache === null) return;
|
|
129424
|
-
if (!this.sensorSliceCarriedATransition(data)) return;
|
|
129739
|
+
if (!await this.sensorSliceCarriedATransition(data)) return;
|
|
129425
129740
|
const cameras = await resolveSensorNotificationCameras(cache, data).catch((err) => {
|
|
129426
129741
|
this.ctx.logger.debug("linked-camera resolution failed — evaluated per row", {
|
|
129427
129742
|
tags: { deviceId: data.deviceId },
|
|
@@ -129589,7 +129904,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
129589
129904
|
* The drops are SUMMARISED, never logged one by one: this path carries every
|
|
129590
129905
|
* camera's native `motion` slice at motion rate.
|
|
129591
129906
|
*/
|
|
129592
|
-
sensorSliceCarriedATransition(data) {
|
|
129907
|
+
async sensorSliceCarriedATransition(data) {
|
|
129593
129908
|
const slice = data.slice;
|
|
129594
129909
|
if (typeof slice !== "object" || slice === null || Array.isArray(slice)) {
|
|
129595
129910
|
this.ctx.logger.debug("sensor state change carried no slice — dropped", {
|
|
@@ -129598,7 +129913,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
129598
129913
|
});
|
|
129599
129914
|
return false;
|
|
129600
129915
|
}
|
|
129601
|
-
const verdict = this.sensorTransitions.
|
|
129916
|
+
const verdict = await this.sensorTransitions.observeSettled(data.deviceId, data.capName, { ...slice });
|
|
129602
129917
|
this.flushSensorTransitionDrops();
|
|
129603
129918
|
if (verdict.changed) {
|
|
129604
129919
|
const entries = Object.entries(slice);
|
|
@@ -129616,6 +129931,35 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
129616
129931
|
return verdict.changed;
|
|
129617
129932
|
}
|
|
129618
129933
|
/**
|
|
129934
|
+
* The last persisted boolean of a level cap, from device-history — the
|
|
129935
|
+
* gate's seed after a restart. Off the event path (the gate starts it at the
|
|
129936
|
+
* first sighting). A cap that declares no history field, or a series with no
|
|
129937
|
+
* point, answers `null`.
|
|
129938
|
+
*/
|
|
129939
|
+
async readPersistedLevel(deviceId, capName) {
|
|
129940
|
+
const key = levelSeriesKey(capName);
|
|
129941
|
+
if (key === null) return null;
|
|
129942
|
+
const to = Date.now();
|
|
129943
|
+
const result = await this.ctx.api.deviceHistory.query.query({
|
|
129944
|
+
deviceId,
|
|
129945
|
+
key,
|
|
129946
|
+
from: to - LEVEL_SEED_WINDOW_MS,
|
|
129947
|
+
to,
|
|
129948
|
+
resolution: "raw",
|
|
129949
|
+
maxPoints: 200
|
|
129950
|
+
});
|
|
129951
|
+
const latest = result.series.flatMap((s) => s.points).reduce((best, p) => best === null || p.t > best.t ? p : best, null);
|
|
129952
|
+
if (latest !== null && latest.last !== null) return {
|
|
129953
|
+
value: latest.last >= .5,
|
|
129954
|
+
since: latest.t
|
|
129955
|
+
};
|
|
129956
|
+
const held = result.series.flatMap((s) => s.before === null ? [] : [s.before]).reduce((best, b) => best === null || b.t > best.t ? b : best, null);
|
|
129957
|
+
return held !== null && held.value !== null ? {
|
|
129958
|
+
value: held.value >= .5,
|
|
129959
|
+
since: held.t
|
|
129960
|
+
} : null;
|
|
129961
|
+
}
|
|
129962
|
+
/**
|
|
129619
129963
|
* One `info` line per (device, cap, reason) per window, or none.
|
|
129620
129964
|
*
|
|
129621
129965
|
* A branch that drops work says so — but this one drops ~0.6 events/s
|