@camstack/addon-post-analysis 1.2.246 → 1.2.247
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{dist-CzmhHYgz.js → dist-4yCJnUsG.js} +52 -1
- package/dist/{dist-BI5QyFCV.mjs → dist-BO-2rxnz.mjs} +52 -1
- 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-S5MTTqwZ.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BtkfkxhR.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Dibaw6Jc.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-CSOvIfGa.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DaqR-pBH.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-DRcnO1Ly.mjs → hostInit-Co4_9jN4.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +235 -15
- package/dist/pipeline-analytics/index.mjs +235 -15
- 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-DyF1G0bN.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_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-4yCJnUsG.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -59327,6 +59327,79 @@ var StationaryEvidenceLedger = class {
|
|
|
59327
59327
|
}
|
|
59328
59328
|
};
|
|
59329
59329
|
//#endregion
|
|
59330
|
+
//#region src/pipeline-analytics/pipeline/stationary/stationary-birth-guard.ts
|
|
59331
|
+
/**
|
|
59332
|
+
* The plausibility bar a stationary BIRTH must clear.
|
|
59333
|
+
*
|
|
59334
|
+
* ## Why a birth needs its own bar
|
|
59335
|
+
*
|
|
59336
|
+
* A birth was guarded by `isPlausibleThumbnailBox`, which only refuses a
|
|
59337
|
+
* NEAR-full-frame box (≥ 0.85 of the frame, or ≥ 0.95 of one dimension). That
|
|
59338
|
+
* is the right question for a thumbnail — "is this crop the whole washed-out
|
|
59339
|
+
* scene?" — and the wrong one for a parked object.
|
|
59340
|
+
*
|
|
59341
|
+
* Measured on device 617, 2026-09-14 08:33: an entry was born with a
|
|
59342
|
+
* 1669x1796 box in a 3840x2160 frame. That is 2,997,524 / 8,294,400 = **0.361
|
|
59343
|
+
* of the frame** — the dawn "envelope exploded" false positive, comfortably
|
|
59344
|
+
* under 0.85, so the thumbnail guard passed it. A third of the camera's view
|
|
59345
|
+
* is not a parked object.
|
|
59346
|
+
*
|
|
59347
|
+
* It did not stop at being wrong about itself. An entry that large reads as a
|
|
59348
|
+
* container: `entrySwallowsDetection` treats any much-smaller contained box as
|
|
59349
|
+
* a FRAGMENT of it, and the scene-sized escape hatch that exists for exactly
|
|
59350
|
+
* this (`SCENE_BOX_AREA_FRAC = 0.55`) sits ABOVE 0.361 — so the duplicate
|
|
59351
|
+
* collapse (D495) folded a genuinely different parked motorcycle into it and
|
|
59352
|
+
* deleted its row.
|
|
59353
|
+
*
|
|
59354
|
+
* ## The number
|
|
59355
|
+
*
|
|
59356
|
+
* {@link BIRTH_MAX_AREA_FRAC} = **0.30**, area fraction, refused at/above.
|
|
59357
|
+
*
|
|
59358
|
+
* - The measured false birth is 0.361 — refused with ~20% of margin, so a
|
|
59359
|
+
* slightly smaller explosion on the same camera is refused too.
|
|
59360
|
+
* - It sits BELOW `SCENE_BOX_AREA_FRAC` (0.55) by design: no entry can be
|
|
59361
|
+
* born into the band [0.30, 0.55) where it is large enough to swallow every
|
|
59362
|
+
* small box in the frame as a "fragment" and not large enough for the
|
|
59363
|
+
* scene-sized guard to disarm it. That band is the whole mechanism of the
|
|
59364
|
+
* 617 motorcycle loss.
|
|
59365
|
+
* - `SCENE_BOX_AREA_FRAC` is NOT moved to do this. It was measured against
|
|
59366
|
+
* device 3829's SUV at ~0.78 of 4K and it decides what the suppression
|
|
59367
|
+
* MATCHER holds; changing it changes what every camera suppresses. This
|
|
59368
|
+
* constant decides only what may be CREATED.
|
|
59369
|
+
* - The cost, stated: a genuine parked object closer to the lens than 30% of
|
|
59370
|
+
* the view no longer earns an entry on that camera (3829's SUV would not be
|
|
59371
|
+
* re-born under this bar). That is the trade — such an object is already
|
|
59372
|
+
* neutered as an entry by the scene-sized guard, while a false one at 0.36
|
|
59373
|
+
* actively destroys neighbouring rows.
|
|
59374
|
+
*
|
|
59375
|
+
* The near-full-frame and full-dimension rules are kept, not replaced: a
|
|
59376
|
+
* 0.96-frame-wide, 0.3-frame-high band is 0.29 of the area and still an
|
|
59377
|
+
* artifact, and `isPlausibleThumbnailBox` is the one place that rule lives.
|
|
59378
|
+
*/
|
|
59379
|
+
/** Frame-area fraction at/above which a box may not be BORN as a parked
|
|
59380
|
+
* object. See the file docblock for the measurements behind 0.30. */
|
|
59381
|
+
var BIRTH_MAX_AREA_FRAC = .3;
|
|
59382
|
+
/** Area of `bbox` as a fraction of the frame. 0 when the frame is unknown —
|
|
59383
|
+
* a fraction of nothing is not a measurement (D393), and the caller that
|
|
59384
|
+
* wants a decision asks {@link isPlausibleStationaryBirthBox}. */
|
|
59385
|
+
function boxAreaFraction(bbox, frameWidth, frameHeight) {
|
|
59386
|
+
if (frameWidth <= 0 || frameHeight <= 0) return 0;
|
|
59387
|
+
return bbox.w * bbox.h / (frameWidth * frameHeight);
|
|
59388
|
+
}
|
|
59389
|
+
/**
|
|
59390
|
+
* True when `bbox` may become a stationary entry.
|
|
59391
|
+
*
|
|
59392
|
+
* Stricter than the best-thumbnail guard and a superset of it: the
|
|
59393
|
+
* near-full-frame / full-dimension artifact rules still apply, plus the
|
|
59394
|
+
* birth-specific area bar. Degenerate frame dimensions (≤0) are plausible —
|
|
59395
|
+
* there is no measurement to refuse on.
|
|
59396
|
+
*/
|
|
59397
|
+
function isPlausibleStationaryBirthBox(bbox, frameWidth, frameHeight) {
|
|
59398
|
+
if (frameWidth <= 0 || frameHeight <= 0) return true;
|
|
59399
|
+
if (!isPlausibleThumbnailBox(bbox, frameWidth, frameHeight)) return false;
|
|
59400
|
+
return boxAreaFraction(bbox, frameWidth, frameHeight) < BIRTH_MAX_AREA_FRAC;
|
|
59401
|
+
}
|
|
59402
|
+
//#endregion
|
|
59330
59403
|
//#region src/pipeline-analytics/pipeline/stationary/stationary-registry.ts
|
|
59331
59404
|
/**
|
|
59332
59405
|
* @durable class=ledger owner=pipeline-analytics
|
|
@@ -59435,6 +59508,7 @@ var StationaryObjectRegistry = class {
|
|
|
59435
59508
|
warmUpMs;
|
|
59436
59509
|
ttlForDevice;
|
|
59437
59510
|
onChange;
|
|
59511
|
+
requestConfirmationLook;
|
|
59438
59512
|
/** Latest processed-frame timestamp per device — expiry counts OBSERVED
|
|
59439
59513
|
* time, not wall-clock. A session-dispatch camera produces no frames
|
|
59440
59514
|
* between motion sessions; that silence is not evidence the object left,
|
|
@@ -59506,6 +59580,7 @@ var StationaryObjectRegistry = class {
|
|
|
59506
59580
|
this.warmUpMs = deps.warmUpMs ?? 2e3;
|
|
59507
59581
|
this.ttlForDevice = deps.ttlForDevice;
|
|
59508
59582
|
this.onChange = deps.onChange;
|
|
59583
|
+
this.requestConfirmationLook = deps.requestConfirmationLook;
|
|
59509
59584
|
}
|
|
59510
59585
|
static declare(store) {
|
|
59511
59586
|
return DurableLedger.declare(store, STATIONARY_SPEC);
|
|
@@ -59675,7 +59750,8 @@ var StationaryObjectRegistry = class {
|
|
|
59675
59750
|
this.onChange?.({
|
|
59676
59751
|
phase: "departed",
|
|
59677
59752
|
entry: e,
|
|
59678
|
-
timestamp: input.timestamp
|
|
59753
|
+
timestamp: input.timestamp,
|
|
59754
|
+
observedAt: input.timestamp - wake.strikeAgeMs
|
|
59679
59755
|
});
|
|
59680
59756
|
}
|
|
59681
59757
|
}
|
|
@@ -59700,7 +59776,7 @@ var StationaryObjectRegistry = class {
|
|
|
59700
59776
|
* has.
|
|
59701
59777
|
*/
|
|
59702
59778
|
async promote(entry, config) {
|
|
59703
|
-
const occupant = findParkedOccupant(this.list(entry.deviceId), entry.className, entry.bbox, (config ?? this.matchConfig).suppressIou, (config ?? this.matchConfig).wakeAssocIou);
|
|
59779
|
+
const occupant = findParkedOccupant(this.censusTenants(entry, this.list(entry.deviceId), config), entry.className, entry.bbox, (config ?? this.matchConfig).suppressIou, (config ?? this.matchConfig).wakeAssocIou);
|
|
59704
59780
|
if (occupant !== void 0) {
|
|
59705
59781
|
const merged = this.absorbInto(occupant, entry);
|
|
59706
59782
|
this.logger.info("stationary promotion absorbed — the spot is already censused", {
|
|
@@ -59725,13 +59801,44 @@ var StationaryObjectRegistry = class {
|
|
|
59725
59801
|
this.onChange?.({
|
|
59726
59802
|
phase: "appeared",
|
|
59727
59803
|
entry,
|
|
59728
|
-
timestamp: entry.becameStationaryAt
|
|
59804
|
+
timestamp: entry.becameStationaryAt,
|
|
59805
|
+
observedAt: entry.becameStationaryAt
|
|
59729
59806
|
});
|
|
59730
59807
|
return {
|
|
59731
59808
|
outcome: "born",
|
|
59732
59809
|
entry
|
|
59733
59810
|
};
|
|
59734
59811
|
}
|
|
59812
|
+
/**
|
|
59813
|
+
* The entries allowed to ABSORB another row — a strict subset of `candidates`.
|
|
59814
|
+
*
|
|
59815
|
+
* An entry that could not be BORN today may not eat its neighbours. Device
|
|
59816
|
+
* 617 held a 1669x1796 box (0.361 of a 4K frame) born before the birth bar
|
|
59817
|
+
* existed; `entrySwallowsDetection` reads every much-smaller contained box as
|
|
59818
|
+
* a fragment of it, and `SCENE_BOX_AREA_FRAC` (0.55) — the guard that exists
|
|
59819
|
+
* to stop exactly this — sits ABOVE 0.361, so the collapse folded a genuinely
|
|
59820
|
+
* different parked motorcycle into it and deleted the motorcycle's row.
|
|
59821
|
+
*
|
|
59822
|
+
* Absorbing is the only thing an oversized entry is refused. It keeps
|
|
59823
|
+
* suppressing, keeps being confirmed and keeps its own row — see
|
|
59824
|
+
* {@link sweep} for why it is not retired.
|
|
59825
|
+
*/
|
|
59826
|
+
censusTenants(candidate, candidates, config) {
|
|
59827
|
+
const tenants = candidates.filter((e) => isPlausibleStationaryBirthBox(e.bbox, e.frameWidth, e.frameHeight));
|
|
59828
|
+
if (tenants.length === candidates.length) return tenants;
|
|
59829
|
+
const wouldHave = findParkedOccupant(candidates.filter((e) => !tenants.includes(e)), candidate.className, candidate.bbox, (config ?? this.matchConfig).suppressIou, (config ?? this.matchConfig).wakeAssocIou);
|
|
59830
|
+
if (wouldHave !== void 0) this.logger.info("stationary row kept apart from an oversized entry", {
|
|
59831
|
+
tags: { deviceId: candidate.deviceId },
|
|
59832
|
+
meta: {
|
|
59833
|
+
deviceId: candidate.deviceId,
|
|
59834
|
+
oversizedEntryId: wouldHave.id,
|
|
59835
|
+
oversizedBbox: wouldHave.bbox,
|
|
59836
|
+
className: candidate.className,
|
|
59837
|
+
candidateBbox: candidate.bbox
|
|
59838
|
+
}
|
|
59839
|
+
});
|
|
59840
|
+
return tenants;
|
|
59841
|
+
}
|
|
59735
59842
|
/** Fold a refused promotion into the entry that already holds its spot. */
|
|
59736
59843
|
absorbInto(occupant, refused) {
|
|
59737
59844
|
const merged = {
|
|
@@ -59745,9 +59852,44 @@ var StationaryObjectRegistry = class {
|
|
|
59745
59852
|
return merged;
|
|
59746
59853
|
}
|
|
59747
59854
|
/**
|
|
59748
|
-
*
|
|
59749
|
-
*
|
|
59750
|
-
*
|
|
59855
|
+
* Does THIS node analyse `deviceId`?
|
|
59856
|
+
*
|
|
59857
|
+
* The registry's ownership signal, and the only one it has: a node receives
|
|
59858
|
+
* inference frames for the cameras it analyses and for no others, so an
|
|
59859
|
+
* observed clock ({@link noteFrame}) is the fact "this camera is mine". It is
|
|
59860
|
+
* the same authority the summary sealer uses (`analysedDeviceIds` off
|
|
59861
|
+
* `lastFrameDimsByDevice`, 2026-09-13, for the same defect on the same
|
|
59862
|
+
* shared table), and it is never cleared per-device, so an idle camera stays
|
|
59863
|
+
* ours and a camera we have never framed never becomes ours by accident.
|
|
59864
|
+
*
|
|
59865
|
+
* What it deliberately is NOT: a per-camera node assignment. `addon-post-
|
|
59866
|
+
* analysis` has none — post-processing is a cluster singleton chosen by
|
|
59867
|
+
* `postProcessingNodeState`, which the registry cannot see and which says
|
|
59868
|
+
* nothing per camera. A node that analyses nothing observes nothing, so this
|
|
59869
|
+
* predicate answers `false` for every row it hydrated, which is the outcome
|
|
59870
|
+
* the singleton would have produced.
|
|
59871
|
+
*/
|
|
59872
|
+
analyses(deviceId) {
|
|
59873
|
+
return this.lastFrameAtByDevice.has(deviceId);
|
|
59874
|
+
}
|
|
59875
|
+
/**
|
|
59876
|
+
* Retire entries unconfirmed for longer than the TTL of OBSERVED time,
|
|
59877
|
+
* collapse duplicate rows, and flush any advanced `lastConfirmedAt`s to the
|
|
59878
|
+
* store. Returns retired entries (for logging).
|
|
59879
|
+
*
|
|
59880
|
+
* ONLY for the cameras this node analyses ({@link analyses}). Every node runs
|
|
59881
|
+
* this addon and every node hydrates the SAME table in {@link load}, so a
|
|
59882
|
+
* sweep over every hydrated device judges — and mutates — cameras it has no
|
|
59883
|
+
* frames for. Measured 2026-09-14: the duplicate collapse for device 617 was
|
|
59884
|
+
* logged by `MinidiGianluca.localdomain` and by `little-unraid`, the same
|
|
59885
|
+
* rows, seven seconds apart.
|
|
59886
|
+
*
|
|
59887
|
+
* An OVERSIZED entry (one that could not be born under the birth bar) is
|
|
59888
|
+
* deliberately NOT retired here. It has been confirmed by frames over time,
|
|
59889
|
+
* which is evidence a birth never had; it cannot be re-born under the new
|
|
59890
|
+
* bar, so the retirement would be permanent and would hand its object back
|
|
59891
|
+
* to the tracker forever. It is refused one thing — absorbing a neighbour
|
|
59892
|
+
* (see {@link censusTenants}) — and otherwise lives out its TTL.
|
|
59751
59893
|
*
|
|
59752
59894
|
* Expiry is measured against the device's latest processed-frame timestamp
|
|
59753
59895
|
* ({@link noteFrame}), NOT the wall clock: a session-dispatch camera emits
|
|
@@ -59759,9 +59901,13 @@ var StationaryObjectRegistry = class {
|
|
|
59759
59901
|
*/
|
|
59760
59902
|
async sweep(now) {
|
|
59761
59903
|
const retired = [];
|
|
59904
|
+
let foreign = 0;
|
|
59762
59905
|
for (const [deviceId, ids] of this.idsByDevice) {
|
|
59763
59906
|
const observedAt = this.lastFrameAtByDevice.get(deviceId);
|
|
59764
|
-
if (observedAt === void 0)
|
|
59907
|
+
if (observedAt === void 0) {
|
|
59908
|
+
foreign += 1;
|
|
59909
|
+
continue;
|
|
59910
|
+
}
|
|
59765
59911
|
const ttl = this.ttlForDevice?.(deviceId) ?? this.entryTtlMs;
|
|
59766
59912
|
for (const id of [...ids]) {
|
|
59767
59913
|
const e = this.ledger.get(id);
|
|
@@ -59787,12 +59933,14 @@ var StationaryObjectRegistry = class {
|
|
|
59787
59933
|
this.onChange?.({
|
|
59788
59934
|
phase: "departed",
|
|
59789
59935
|
entry: e,
|
|
59790
|
-
timestamp: now
|
|
59936
|
+
timestamp: now,
|
|
59937
|
+
observedAt: e.lastConfirmedAt
|
|
59791
59938
|
});
|
|
59792
59939
|
}
|
|
59793
59940
|
}
|
|
59794
59941
|
if (ids.size === 0) this.idsByDevice.delete(deviceId);
|
|
59795
59942
|
}
|
|
59943
|
+
if (foreign > 0) this.logger.debug("stationary sweep skipped cameras this node does not analyse", { meta: { devices: foreign } });
|
|
59796
59944
|
const countedDevices = new Set([...this.absorbedSinceSweep.keys(), ...this.suppressedSinceSweep.keys()]);
|
|
59797
59945
|
for (const deviceId of countedDevices) {
|
|
59798
59946
|
const absorbed = this.absorbedSinceSweep.get(deviceId) ?? 0;
|
|
@@ -59830,7 +59978,10 @@ var StationaryObjectRegistry = class {
|
|
|
59830
59978
|
}
|
|
59831
59979
|
});
|
|
59832
59980
|
this.strikeCountersByDevice.clear();
|
|
59833
|
-
for (const deviceId of [...this.idsByDevice.keys()])
|
|
59981
|
+
for (const deviceId of [...this.idsByDevice.keys()]) {
|
|
59982
|
+
if (!this.analyses(deviceId)) continue;
|
|
59983
|
+
this.collapseDuplicates(deviceId);
|
|
59984
|
+
}
|
|
59834
59985
|
for (const id of [...this.dirty]) {
|
|
59835
59986
|
this.dirty.delete(id);
|
|
59836
59987
|
const entry = this.ledger.get(id);
|
|
@@ -59944,6 +60095,12 @@ var StationaryObjectRegistry = class {
|
|
|
59944
60095
|
confirmMinGapMs: this.departureConfig.confirmMinGapMs
|
|
59945
60096
|
}
|
|
59946
60097
|
});
|
|
60098
|
+
this.requestConfirmationLook?.({
|
|
60099
|
+
deviceId,
|
|
60100
|
+
entryId: change.entryId,
|
|
60101
|
+
afterMs: this.departureConfig.confirmMinGapMs,
|
|
60102
|
+
frames: this.departureConfig.absenceMinReads
|
|
60103
|
+
});
|
|
59947
60104
|
}
|
|
59948
60105
|
}
|
|
59949
60106
|
if (pending !== void 0 && pending.size === 0) this.departureStrikes.delete(deviceId);
|
|
@@ -59991,7 +60148,7 @@ var StationaryObjectRegistry = class {
|
|
|
59991
60148
|
if (entries.length < 2) return;
|
|
59992
60149
|
const survivors = [];
|
|
59993
60150
|
for (const candidate of entries) {
|
|
59994
|
-
const occupant = findParkedOccupant(survivors, candidate.className, candidate.bbox, this.matchConfig.suppressIou, this.matchConfig.wakeAssocIou);
|
|
60151
|
+
const occupant = findParkedOccupant(this.censusTenants(candidate, survivors), candidate.className, candidate.bbox, this.matchConfig.suppressIou, this.matchConfig.wakeAssocIou);
|
|
59995
60152
|
if (occupant === void 0) {
|
|
59996
60153
|
survivors.push(candidate);
|
|
59997
60154
|
continue;
|
|
@@ -75618,6 +75775,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75618
75775
|
* failure mode `observe` mode was already in). See `store/lts-store.ts`.
|
|
75619
75776
|
*/
|
|
75620
75777
|
lts = null;
|
|
75778
|
+
/** The runner node that last produced a frame for a camera — see
|
|
75779
|
+
* `handleInferenceResult`. */
|
|
75780
|
+
lastRunnerNodeByDevice = /* @__PURE__ */ new Map();
|
|
75781
|
+
/** Pending out-of-band confirmation looks, keyed `deviceId:entryId`, so an
|
|
75782
|
+
* entry that re-arms while one is in flight does not stack timers. */
|
|
75783
|
+
pendingConfirmationLooks = /* @__PURE__ */ new Map();
|
|
75621
75784
|
ltsFlushTimer = null;
|
|
75622
75785
|
/**
|
|
75623
75786
|
* `scene-monitor` — the operator's latched boolean over a reference region.
|
|
@@ -76181,7 +76344,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
76181
76344
|
store: api.settingsStore,
|
|
76182
76345
|
logger: logger.child("StationaryRegistry"),
|
|
76183
76346
|
ttlForDevice: (deviceId) => this.stationarySettingsFromCache(deviceId).entryTtlMs,
|
|
76184
|
-
|
|
76347
|
+
requestConfirmationLook: (ask) => this.requestConfirmationLook(ask),
|
|
76348
|
+
onChange: ({ phase, entry, timestamp, observedAt }) => {
|
|
76185
76349
|
this.ctx.eventBus.emit({
|
|
76186
76350
|
id: `pa-stationary-${entry.id}-${phase}`,
|
|
76187
76351
|
timestamp: new Date(timestamp),
|
|
@@ -76196,7 +76360,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
76196
76360
|
entryId: entry.id,
|
|
76197
76361
|
className: entry.className,
|
|
76198
76362
|
phase,
|
|
76199
|
-
timestamp
|
|
76363
|
+
timestamp,
|
|
76364
|
+
observedAt
|
|
76200
76365
|
}
|
|
76201
76366
|
});
|
|
76202
76367
|
this.packageDropDetector?.onStationaryChange({
|
|
@@ -78377,9 +78542,59 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
78377
78542
|
this.ncArtifactIndex = null;
|
|
78378
78543
|
this.ncArtifactStore = null;
|
|
78379
78544
|
}
|
|
78545
|
+
/**
|
|
78546
|
+
* Ask the runner to look at a camera once more, shortly after a departure was
|
|
78547
|
+
* armed — see `StationaryRegistryDeps.requestConfirmationLook` for why, and
|
|
78548
|
+
* the cap's `requestOccupancyBurst` for the two numbers that shape the ask.
|
|
78549
|
+
*
|
|
78550
|
+
* Pinned to the node whose frames we have been analysing: an unpinned call
|
|
78551
|
+
* would be served by whichever runner answers, and a runner that does not
|
|
78552
|
+
* hold the camera refuses it — correctly, but uselessly.
|
|
78553
|
+
*
|
|
78554
|
+
* Fire-and-forget by construction. This is an OPTIMISATION of a path that
|
|
78555
|
+
* already terminates on its own (the entry ages out), so a failure here must
|
|
78556
|
+
* cost nothing but a log line.
|
|
78557
|
+
*/
|
|
78558
|
+
requestConfirmationLook(ask) {
|
|
78559
|
+
const key = `${String(ask.deviceId)}:${ask.entryId}`;
|
|
78560
|
+
if (this.pendingConfirmationLooks.has(key)) return;
|
|
78561
|
+
const nodeId = this.lastRunnerNodeByDevice.get(ask.deviceId);
|
|
78562
|
+
const runnerApi = this.ctx.api.pipelineRunner;
|
|
78563
|
+
if (nodeId === void 0 || !runnerApi?.requestOccupancyBurst) return;
|
|
78564
|
+
const timer = setTimeout(() => {
|
|
78565
|
+
this.pendingConfirmationLooks.delete(key);
|
|
78566
|
+
if (this.shuttingDown) return;
|
|
78567
|
+
runnerApi.requestOccupancyBurst.mutate({
|
|
78568
|
+
deviceId: ask.deviceId,
|
|
78569
|
+
frames: ask.frames,
|
|
78570
|
+
reason: "stationary-departure-confirm"
|
|
78571
|
+
}, require_dist.nodePin(nodeId)).then((answer) => {
|
|
78572
|
+
if (!answer.started) this.ctx.logger.debug("departure confirmation look refused", {
|
|
78573
|
+
tags: { deviceId: ask.deviceId },
|
|
78574
|
+
meta: {
|
|
78575
|
+
entryId: ask.entryId,
|
|
78576
|
+
nodeId,
|
|
78577
|
+
refusedBecause: answer.refusedBecause
|
|
78578
|
+
}
|
|
78579
|
+
});
|
|
78580
|
+
}).catch((err) => {
|
|
78581
|
+
this.ctx.logger.debug("departure confirmation look failed", {
|
|
78582
|
+
tags: { deviceId: ask.deviceId },
|
|
78583
|
+
meta: {
|
|
78584
|
+
entryId: ask.entryId,
|
|
78585
|
+
nodeId,
|
|
78586
|
+
error: String(err)
|
|
78587
|
+
}
|
|
78588
|
+
});
|
|
78589
|
+
});
|
|
78590
|
+
}, ask.afterMs);
|
|
78591
|
+
timer.unref?.();
|
|
78592
|
+
this.pendingConfirmationLooks.set(key, timer);
|
|
78593
|
+
}
|
|
78380
78594
|
async handleInferenceResult(data) {
|
|
78381
78595
|
if (this.shuttingDown) return;
|
|
78382
78596
|
const { deviceId, frame } = data;
|
|
78597
|
+
if (data.nodeId.length > 0) this.lastRunnerNodeByDevice.set(deviceId, data.nodeId);
|
|
78383
78598
|
const lag = this.frameLag.observe(deviceId, Date.now(), data.emittedAt, data.capturedAt);
|
|
78384
78599
|
if (lag !== null) {
|
|
78385
78600
|
const verdict = lagVerdict(lag);
|
|
@@ -81908,13 +82123,18 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
81908
82123
|
const { deviceId, key, processor, track, row, firstSeen, frameWidth, frameHeight, timestamp } = input;
|
|
81909
82124
|
const label = row.label;
|
|
81910
82125
|
const keyFrameMediaId = row.keyFrameMediaId ?? this.residents.keyFrameMediaId(track.trackId);
|
|
81911
|
-
const plausible =
|
|
81912
|
-
if (!plausible) this.ctx.logger.info("stationary birth rejected
|
|
82126
|
+
const plausible = isPlausibleStationaryBirthBox(row.bestBbox, frameWidth, frameHeight);
|
|
82127
|
+
if (!plausible) this.ctx.logger.info("stationary birth rejected — implausible box for a parked object", {
|
|
81913
82128
|
tags: { deviceId },
|
|
81914
82129
|
meta: {
|
|
82130
|
+
deviceId,
|
|
81915
82131
|
trackId: track.trackId,
|
|
81916
82132
|
className: row.className,
|
|
81917
82133
|
bbox: row.bestBbox,
|
|
82134
|
+
frameWidth,
|
|
82135
|
+
frameHeight,
|
|
82136
|
+
areaFrac: Number(boxAreaFraction(row.bestBbox, frameWidth, frameHeight).toFixed(3)),
|
|
82137
|
+
maxAreaFrac: BIRTH_MAX_AREA_FRAC,
|
|
81918
82138
|
agreed: row.agreed,
|
|
81919
82139
|
seen: row.seen
|
|
81920
82140
|
}
|