@camstack/addon-post-analysis 1.2.211 → 1.2.213
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-D2rFaMex.mjs → dist-CT2SouNJ.mjs} +294 -2
- package/dist/{dist-FHSaZkf_.js → dist-DA1sJKXM.js} +311 -1
- 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-4gDJk8ED.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CxVox7bu.mjs} +3 -3
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B2Kde85q.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CcE47LPf.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-Dgzccgln.mjs → hostInit-BSVhT9bi.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +745 -98
- package/dist/pipeline-analytics/index.mjs +745 -98
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -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-DA1sJKXM.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");
|
|
@@ -34861,6 +34861,9 @@ var DEFAULT_TRACKER_CONFIG = {
|
|
|
34861
34861
|
var PERSON_CLASS = "person";
|
|
34862
34862
|
var ANIMAL_CLASS = "animal";
|
|
34863
34863
|
var MAX_PATH_LENGTH = 300;
|
|
34864
|
+
/** No stationary-held detections on this frame — the default for every caller
|
|
34865
|
+
* that has no gate (replay, tests, the non-pipeline planes). */
|
|
34866
|
+
var EMPTY_INDEX_SET = /* @__PURE__ */ new Set();
|
|
34864
34867
|
function clamp$1(value, min, max) {
|
|
34865
34868
|
return Math.max(min, Math.min(max, value));
|
|
34866
34869
|
}
|
|
@@ -35480,13 +35483,15 @@ var SortTracker = class SortTracker {
|
|
|
35480
35483
|
* gates) so the two-tier ByteTrack association stays a single implementation.
|
|
35481
35484
|
* Mutates `used` / `matchedTracks` / `matched` in place.
|
|
35482
35485
|
*/
|
|
35483
|
-
greedyAssociate(detections, candidateIdxs, iouThreshold, timestamp, used, matchedTracks, matched) {
|
|
35486
|
+
greedyAssociate(detections, candidateIdxs, iouThreshold, timestamp, used, matchedTracks, matched, continuationOnly) {
|
|
35484
35487
|
const pairs = [];
|
|
35485
35488
|
for (const track of this.tracks) {
|
|
35486
35489
|
if (matchedTracks.has(track)) continue;
|
|
35487
35490
|
const pbox = this.predicted(track, timestamp);
|
|
35491
|
+
const confirmed = this.isConfirmedForEmit(track);
|
|
35488
35492
|
for (const di of candidateIdxs) {
|
|
35489
35493
|
if (used.has(di)) continue;
|
|
35494
|
+
if (!confirmed && continuationOnly.has(di)) continue;
|
|
35490
35495
|
const det = detections[di];
|
|
35491
35496
|
if (!this.associable(track.class, det)) continue;
|
|
35492
35497
|
const score = iou$3(pbox, det.bbox);
|
|
@@ -35507,6 +35512,7 @@ var SortTracker = class SortTracker {
|
|
|
35507
35512
|
}
|
|
35508
35513
|
update(detections, timestamp, frameContext) {
|
|
35509
35514
|
const frameArea = frameContext ? frameContext.frameWidth * frameContext.frameHeight : 0;
|
|
35515
|
+
const continuationOnly = frameContext?.continuationOnly ?? EMPTY_INDEX_SET;
|
|
35510
35516
|
const resurrectedScene = /* @__PURE__ */ new Map();
|
|
35511
35517
|
if (this.config.maxTrackLifetimeMs > 0) {
|
|
35512
35518
|
const alive = [];
|
|
@@ -35543,12 +35549,12 @@ var SortTracker = class SortTracker {
|
|
|
35543
35549
|
for (let di = 0; di < detections.length; di++) if (this.config.byteTrackEnabled && detections[di].score < this.config.byteTrackHighThreshold) lowIdxs.push(di);
|
|
35544
35550
|
else highIdxs.push(di);
|
|
35545
35551
|
const lowSet = new Set(lowIdxs);
|
|
35546
|
-
this.greedyAssociate(detections, highIdxs, this.config.iouThreshold, timestamp, used, matchedTracks, matched);
|
|
35552
|
+
this.greedyAssociate(detections, highIdxs, this.config.iouThreshold, timestamp, used, matchedTracks, matched, continuationOnly);
|
|
35547
35553
|
const rescuePairs = [];
|
|
35548
35554
|
for (const track of this.tracks) {
|
|
35549
35555
|
if (matchedTracks.has(track)) continue;
|
|
35550
35556
|
for (let di = 0; di < detections.length; di++) {
|
|
35551
|
-
if (used.has(di) || lowSet.has(di)) continue;
|
|
35557
|
+
if (used.has(di) || lowSet.has(di) || continuationOnly.has(di)) continue;
|
|
35552
35558
|
const det = detections[di];
|
|
35553
35559
|
const gate = this.looseMatch(track, det, timestamp);
|
|
35554
35560
|
if (gate === null) continue;
|
|
@@ -35570,7 +35576,7 @@ var SortTracker = class SortTracker {
|
|
|
35570
35576
|
used.add(pair.detIdx);
|
|
35571
35577
|
this.reportBind("rescue", pair.gate, pair.track, det, timestamp);
|
|
35572
35578
|
}
|
|
35573
|
-
if (this.config.byteTrackEnabled && lowIdxs.length > 0) this.greedyAssociate(detections, lowIdxs, this.config.byteTrackLowIouThreshold, timestamp, used, matchedTracks, matched);
|
|
35579
|
+
if (this.config.byteTrackEnabled && lowIdxs.length > 0) this.greedyAssociate(detections, lowIdxs, this.config.byteTrackLowIouThreshold, timestamp, used, matchedTracks, matched, continuationOnly);
|
|
35574
35580
|
for (const [track, det] of matched) {
|
|
35575
35581
|
if (track.class !== det.class) this.reportRefusal("cross-class-absorbed", det, { against: track });
|
|
35576
35582
|
const prevCenter = bboxCentroid({
|
|
@@ -35626,7 +35632,7 @@ var SortTracker = class SortTracker {
|
|
|
35626
35632
|
} else surviving.push(track);
|
|
35627
35633
|
}
|
|
35628
35634
|
for (let di = 0; di < detections.length; di++) {
|
|
35629
|
-
if (used.has(di)) continue;
|
|
35635
|
+
if (used.has(di) || continuationOnly.has(di)) continue;
|
|
35630
35636
|
const det = detections[di];
|
|
35631
35637
|
let best;
|
|
35632
35638
|
let bestScore = -1;
|
|
@@ -35694,6 +35700,11 @@ var SortTracker = class SortTracker {
|
|
|
35694
35700
|
});
|
|
35695
35701
|
for (const di of unmatchedIdx) {
|
|
35696
35702
|
const det = detections[di];
|
|
35703
|
+
if (continuationOnly.has(di)) {
|
|
35704
|
+
this.reportRefusal("stationary-hold", det);
|
|
35705
|
+
used.add(di);
|
|
35706
|
+
continue;
|
|
35707
|
+
}
|
|
35697
35708
|
if (!this.meetsClassScoreFloor(det)) {
|
|
35698
35709
|
this.reportRefusal("score-floor", det, {
|
|
35699
35710
|
threshold: this.config.classMinScores[det.class] ?? 0,
|
|
@@ -36432,14 +36443,28 @@ var FrameProcessor = class {
|
|
|
36432
36443
|
timestamp
|
|
36433
36444
|
}) : {
|
|
36434
36445
|
suppressedIndices: /* @__PURE__ */ new Set(),
|
|
36446
|
+
continuableIndices: /* @__PURE__ */ new Set(),
|
|
36435
36447
|
confirmed: [],
|
|
36436
36448
|
woken: []
|
|
36437
36449
|
};
|
|
36438
|
-
const trackerInput =
|
|
36450
|
+
const trackerInput = [];
|
|
36451
|
+
const continuationOnly = /* @__PURE__ */ new Set();
|
|
36452
|
+
const continuableBoxes = /* @__PURE__ */ new Set();
|
|
36453
|
+
for (let i = 0; i < filteredDetections.length; i++) {
|
|
36454
|
+
const det = filteredDetections[i];
|
|
36455
|
+
if (gate.suppressedIndices.has(i)) {
|
|
36456
|
+
if (!gate.continuableIndices.has(i)) continue;
|
|
36457
|
+
continuationOnly.add(trackerInput.length);
|
|
36458
|
+
continuableBoxes.add(det.bbox);
|
|
36459
|
+
}
|
|
36460
|
+
trackerInput.push(det);
|
|
36461
|
+
}
|
|
36439
36462
|
const trackedDetections = this.tracker.update(trackerInput, timestamp, {
|
|
36440
36463
|
frameWidth,
|
|
36441
|
-
frameHeight
|
|
36464
|
+
frameHeight,
|
|
36465
|
+
continuationOnly
|
|
36442
36466
|
});
|
|
36467
|
+
const stationaryContinued = continuableBoxes.size === 0 ? 0 : trackedDetections.filter((t) => t.matchedThisFrame !== false && continuableBoxes.has(t.bbox)).length;
|
|
36443
36468
|
const riderFolds = foldByVehicleBbox.size === 0 ? EMPTY_RIDER_FOLDS : [...foldByVehicleBbox].map(([bbox, fold]) => {
|
|
36444
36469
|
const td = trackedDetections.find((t) => t.bbox === bbox && t.matchedThisFrame !== false);
|
|
36445
36470
|
return td !== void 0 ? {
|
|
@@ -36643,6 +36668,7 @@ var FrameProcessor = class {
|
|
|
36643
36668
|
rawTrackedDetections: trackedDetections,
|
|
36644
36669
|
stationaryConfirmed: gate.confirmed,
|
|
36645
36670
|
stationaryWoken: gate.woken,
|
|
36671
|
+
stationaryContinued,
|
|
36646
36672
|
riderFolds
|
|
36647
36673
|
};
|
|
36648
36674
|
}
|
|
@@ -53432,6 +53458,95 @@ var BirthEvidenceStore = class {
|
|
|
53432
53458
|
}
|
|
53433
53459
|
};
|
|
53434
53460
|
//#endregion
|
|
53461
|
+
//#region src/pipeline-analytics/pipeline/birth-decision-record.ts
|
|
53462
|
+
/**
|
|
53463
|
+
* birth-decision-record — turn one resolved birth verdict into the row that is
|
|
53464
|
+
* kept (D409).
|
|
53465
|
+
*
|
|
53466
|
+
* PURE, and separated from the store for the usual reason: the two rules worth
|
|
53467
|
+
* getting right — what counts as the fail-open, and when the latency proxy is
|
|
53468
|
+
* allowed to report a number — are decisions, not I/O, and a decision that only
|
|
53469
|
+
* exists inside a SQLite call cannot be tested or argued with.
|
|
53470
|
+
*
|
|
53471
|
+
* MEASUREMENT ONLY. Nothing here gates a birth, changes a verdict, or feeds
|
|
53472
|
+
* back into the frame path. The caller writes the row after the verdict is
|
|
53473
|
+
* already taken.
|
|
53474
|
+
*/
|
|
53475
|
+
/**
|
|
53476
|
+
* The verdict as the ledger reports it.
|
|
53477
|
+
*
|
|
53478
|
+
* `exhausted-fallback` is a CONFIRMATION and is counted apart from one, because
|
|
53479
|
+
* a birth nobody ever looked at and a birth measured and passed are the same
|
|
53480
|
+
* row in every existing aggregate — which is what made "the gate stopped
|
|
53481
|
+
* failing open" and "the gate now suppresses everything it cannot measure"
|
|
53482
|
+
* indistinguishable. Exhaustion attached to a SUPPRESSION is meaningless
|
|
53483
|
+
* (exhaustion only ever waves a birth through) and is discarded rather than
|
|
53484
|
+
* inventing a fourth member.
|
|
53485
|
+
*/
|
|
53486
|
+
function verdictFor(input) {
|
|
53487
|
+
if (!input.confirmed) return "suppressed";
|
|
53488
|
+
return input.exhaustedFallback ? "exhausted-fallback" : "confirmed";
|
|
53489
|
+
}
|
|
53490
|
+
/**
|
|
53491
|
+
* The latency proxy, and the three ways it declines to produce a number.
|
|
53492
|
+
*
|
|
53493
|
+
* It reports `null` — never a fabricated or clamped value — when there is no
|
|
53494
|
+
* track (a suppressed birth), no motion reference on this runner, or a
|
|
53495
|
+
* reference older than {@link MAX_BIRTH_LATENCY_PROXY_MS}. That last case is
|
|
53496
|
+
* the one that matters: on a busy scene the burst never closes and the onset is
|
|
53497
|
+
* minutes old, and a minutes-long "birth latency" would be read as the
|
|
53498
|
+
* detector's fault when it is the proxy's. When the reference is refused, the
|
|
53499
|
+
* onset and the burst index go with it — reporting an index against a reference
|
|
53500
|
+
* this row does not carry would be an invitation to join them back up.
|
|
53501
|
+
*
|
|
53502
|
+
* A NEGATIVE result is returned as measured. The analyzer's pixel-count floor
|
|
53503
|
+
* can be crossed after the detector's confidence floor for a subject entering
|
|
53504
|
+
* slowly at the frame edge, and clamping that away would delete precisely the
|
|
53505
|
+
* evidence that the proxy is unreliable. See `BirthDecisionRecordSchema` for
|
|
53506
|
+
* the full error bars.
|
|
53507
|
+
*/
|
|
53508
|
+
function latencyFor(input, firstSeen) {
|
|
53509
|
+
const onset = input.motionOnsetAtMs;
|
|
53510
|
+
if (firstSeen === null || onset === null) return {
|
|
53511
|
+
motionOnsetAt: null,
|
|
53512
|
+
birthLatencyMs: null,
|
|
53513
|
+
birthIndexInBurst: null
|
|
53514
|
+
};
|
|
53515
|
+
if (firstSeen - onset > 6e4) return {
|
|
53516
|
+
motionOnsetAt: null,
|
|
53517
|
+
birthLatencyMs: null,
|
|
53518
|
+
birthIndexInBurst: null
|
|
53519
|
+
};
|
|
53520
|
+
return {
|
|
53521
|
+
motionOnsetAt: onset,
|
|
53522
|
+
birthLatencyMs: firstSeen - onset,
|
|
53523
|
+
birthIndexInBurst: input.birthIndexInBurst
|
|
53524
|
+
};
|
|
53525
|
+
}
|
|
53526
|
+
/** Build the durable row for one resolved birth decision. */
|
|
53527
|
+
function buildBirthDecisionRecord(input) {
|
|
53528
|
+
const verdict = verdictFor(input);
|
|
53529
|
+
const firstSeen = verdict === "suppressed" ? null : input.firstSeen;
|
|
53530
|
+
const latency = latencyFor(input, firstSeen);
|
|
53531
|
+
return {
|
|
53532
|
+
at: input.decidedAtMs,
|
|
53533
|
+
deviceId: input.deviceId,
|
|
53534
|
+
sourceTrackId: input.trackId,
|
|
53535
|
+
className: input.className,
|
|
53536
|
+
verdict,
|
|
53537
|
+
reason: input.reason ?? null,
|
|
53538
|
+
attempts: input.attempts,
|
|
53539
|
+
deferredForMs: input.deferredForMs,
|
|
53540
|
+
decidedOnCoastedFrame: input.matchedThisFrame === false,
|
|
53541
|
+
birthEvidenceAvailable: input.birthEvidenceAvailable,
|
|
53542
|
+
decidedByBirthEvidence: input.decidedByBirthEvidence,
|
|
53543
|
+
bestScore: input.bestScore ?? null,
|
|
53544
|
+
appliedMinConfidence: input.appliedMinConfidence ?? null,
|
|
53545
|
+
firstSeen,
|
|
53546
|
+
...latency
|
|
53547
|
+
};
|
|
53548
|
+
}
|
|
53549
|
+
//#endregion
|
|
53435
53550
|
//#region src/pipeline-analytics/pipeline/deferred-births.ts
|
|
53436
53551
|
var DeferredBirthRegistry = class {
|
|
53437
53552
|
byDevice = /* @__PURE__ */ new Map();
|
|
@@ -53506,6 +53621,44 @@ var DeferredBirthRegistry = class {
|
|
|
53506
53621
|
}
|
|
53507
53622
|
};
|
|
53508
53623
|
//#endregion
|
|
53624
|
+
//#region src/pipeline-analytics/pipeline/motion-onset-registry.ts
|
|
53625
|
+
var MotionOnsetRegistry = class {
|
|
53626
|
+
byDevice = /* @__PURE__ */ new Map();
|
|
53627
|
+
/**
|
|
53628
|
+
* Motion went off→on at `atMs`. Opens a new burst, discarding the previous
|
|
53629
|
+
* one's birth count — a new burst is a new subject population.
|
|
53630
|
+
*/
|
|
53631
|
+
noteRisingEdge(deviceId, atMs) {
|
|
53632
|
+
this.byDevice.set(deviceId, {
|
|
53633
|
+
onsetAtMs: atMs,
|
|
53634
|
+
births: 0
|
|
53635
|
+
});
|
|
53636
|
+
}
|
|
53637
|
+
/** The open burst's onset, or `null` for a device with no reference. */
|
|
53638
|
+
onsetFor(deviceId) {
|
|
53639
|
+
return this.byDevice.get(deviceId)?.onsetAtMs ?? null;
|
|
53640
|
+
}
|
|
53641
|
+
/**
|
|
53642
|
+
* Count one birth decision against this device's burst and return its 0-based
|
|
53643
|
+
* index — `null` when there is no burst to count it against.
|
|
53644
|
+
*
|
|
53645
|
+
* Index 0 is the only index at which the burst plausibly belongs to the same
|
|
53646
|
+
* subject as the birth; the ledger records the index rather than filtering
|
|
53647
|
+
* here, so the reader can see how often it is not 0.
|
|
53648
|
+
*/
|
|
53649
|
+
noteBirth(deviceId) {
|
|
53650
|
+
const burst = this.byDevice.get(deviceId);
|
|
53651
|
+
if (burst === void 0) return null;
|
|
53652
|
+
const index = burst.births;
|
|
53653
|
+
burst.births += 1;
|
|
53654
|
+
return index;
|
|
53655
|
+
}
|
|
53656
|
+
/** Drop a device's reference (device removed / pipeline reset). */
|
|
53657
|
+
forgetDevice(deviceId) {
|
|
53658
|
+
this.byDevice.delete(deviceId);
|
|
53659
|
+
}
|
|
53660
|
+
};
|
|
53661
|
+
//#endregion
|
|
53509
53662
|
//#region src/pipeline-analytics/pipeline/detail-semantic-gate.ts
|
|
53510
53663
|
/** The detail `className` whose result IS the recognition. */
|
|
53511
53664
|
var PLATE_CLASS = "plate";
|
|
@@ -54862,6 +55015,31 @@ function thumbnailWouldShowParkedNotSubject(input) {
|
|
|
54862
55015
|
* dropped, against every entry that did NOT wake this frame. Woken entries are
|
|
54863
55016
|
* excluded so a departure still releases its object — a car that drives away is
|
|
54864
55017
|
* noticed on the frame its second read lands, exactly as before.
|
|
55018
|
+
*
|
|
55019
|
+
* ## A weak hold may CONTINUE a track; only a strong read is the object (D410)
|
|
55020
|
+
*
|
|
55021
|
+
* D275 predicted that a moving vehicle overlapping a parked one "loses a frame
|
|
55022
|
+
* or two while it passes" and coasts through on `maxAge`. Measured on device
|
|
55023
|
+
* 617 on 2026-09-08 it lost the whole pass: the parked cars sit at the frame
|
|
55024
|
+
* edge, so a car driving across their front is held on every frame from the
|
|
55025
|
+
* first overlap to the moment it leaves the frame, and the tracker — which had
|
|
55026
|
+
* followed it for 5–10 frames — coasts `maxMissedMs` on nothing and drops it.
|
|
55027
|
+
* Four operator-marked tracks, 1.7–3.5 s old, died on exactly that geometry;
|
|
55028
|
+
* the sweep for the window reported `reach: 0`, so the hold was pass 1's: the
|
|
55029
|
+
* mover occludes the parked car, the detector reports one vehicle box, and it
|
|
55030
|
+
* is the entry's best match.
|
|
55031
|
+
*
|
|
55032
|
+
* The hold is right — that box covers the parked spot and must not OPEN a
|
|
55033
|
+
* track, which is the whole of the operator's requirement — but it is not the
|
|
55034
|
+
* parked car: a strong read (IoU ≥ `suppressIou`) is the parked object unmoved,
|
|
55035
|
+
* and everything below it is a box that merely overlaps the spot (swallow,
|
|
55036
|
+
* jitter, departing). So `suppressedIndices` still names every box that may
|
|
55037
|
+
* not spawn, and `continuableIndices` names the weak subset the tracker may use
|
|
55038
|
+
* to CONTINUE a track it has already confirmed — through its strict
|
|
55039
|
+
* predicted-box association only, never a rescue or a resurrection. The reach
|
|
55040
|
+
* pass reports strength the same way. Nothing about confirmation, anchoring or
|
|
55041
|
+
* departure changes: a weak hold confirms and never re-anchors, exactly as
|
|
55042
|
+
* before.
|
|
54865
55043
|
*/
|
|
54866
55044
|
function partitionDetectionsAgainstRegistry(input) {
|
|
54867
55045
|
const { entries, detections, config, now } = input;
|
|
@@ -54869,6 +55047,7 @@ function partitionDetectionsAgainstRegistry(input) {
|
|
|
54869
55047
|
const departure = input.departure ?? DEFAULT_DEPARTURE_CONFIG;
|
|
54870
55048
|
const warmUp = input.warmUp ?? false;
|
|
54871
55049
|
const suppressed = /* @__PURE__ */ new Set();
|
|
55050
|
+
const continuable = /* @__PURE__ */ new Set();
|
|
54872
55051
|
const confirmed = [];
|
|
54873
55052
|
const woken = [];
|
|
54874
55053
|
const strikeChanges = [];
|
|
@@ -54908,6 +55087,7 @@ function partitionDetectionsAgainstRegistry(input) {
|
|
|
54908
55087
|
}
|
|
54909
55088
|
if (holdIdx >= 0) {
|
|
54910
55089
|
suppressed.add(holdIdx);
|
|
55090
|
+
if (!strong) continuable.add(holdIdx);
|
|
54911
55091
|
confirmed.push({
|
|
54912
55092
|
entryId: entry.id,
|
|
54913
55093
|
className: entry.className,
|
|
@@ -54947,6 +55127,7 @@ function partitionDetectionsAgainstRegistry(input) {
|
|
|
54947
55127
|
if (warmUp) {
|
|
54948
55128
|
warmUpDepartureReads += 1;
|
|
54949
55129
|
suppressed.add(bestIdx);
|
|
55130
|
+
continuable.add(bestIdx);
|
|
54950
55131
|
continue;
|
|
54951
55132
|
}
|
|
54952
55133
|
if (strike === void 0) {
|
|
@@ -54959,6 +55140,7 @@ function partitionDetectionsAgainstRegistry(input) {
|
|
|
54959
55140
|
reason: "armed"
|
|
54960
55141
|
});
|
|
54961
55142
|
suppressed.add(bestIdx);
|
|
55143
|
+
continuable.add(bestIdx);
|
|
54962
55144
|
continue;
|
|
54963
55145
|
}
|
|
54964
55146
|
if (strikeAgeMs >= departure.confirmMinGapMs) {
|
|
@@ -54979,6 +55161,7 @@ function partitionDetectionsAgainstRegistry(input) {
|
|
|
54979
55161
|
reason: "held"
|
|
54980
55162
|
});
|
|
54981
55163
|
suppressed.add(bestIdx);
|
|
55164
|
+
continuable.add(bestIdx);
|
|
54982
55165
|
}
|
|
54983
55166
|
const wokenIds = woken.length === 0 ? EMPTY_IDS : new Set(woken.map((w) => w.entryId));
|
|
54984
55167
|
let reachSuppressedCount = 0;
|
|
@@ -54989,14 +55172,17 @@ function partitionDetectionsAgainstRegistry(input) {
|
|
|
54989
55172
|
for (let ei = 0; ei < entries.length; ei++) {
|
|
54990
55173
|
const entry = entries[ei];
|
|
54991
55174
|
if (entryClasses[ei] !== detClass || wokenIds.has(entry.id)) continue;
|
|
54992
|
-
|
|
55175
|
+
const held = parkedEntryHoldsDetection(entry, det.bbox, config);
|
|
55176
|
+
if (!held.holds) continue;
|
|
54993
55177
|
suppressed.add(di);
|
|
55178
|
+
if (!held.strong) continuable.add(di);
|
|
54994
55179
|
reachSuppressedCount += 1;
|
|
54995
55180
|
break;
|
|
54996
55181
|
}
|
|
54997
55182
|
}
|
|
54998
55183
|
return {
|
|
54999
55184
|
suppressedIndices: suppressed,
|
|
55185
|
+
continuableIndices: continuable,
|
|
55000
55186
|
confirmed,
|
|
55001
55187
|
woken,
|
|
55002
55188
|
strikeChanges,
|
|
@@ -56620,6 +56806,10 @@ var StationaryObjectRegistry = class {
|
|
|
56620
56806
|
*/
|
|
56621
56807
|
suppressedSinceSweep = /* @__PURE__ */ new Map();
|
|
56622
56808
|
reachSuppressedSinceSweep = /* @__PURE__ */ new Map();
|
|
56809
|
+
/** Weak holds handed to the tracker as continuable (D410), per device. */
|
|
56810
|
+
continuableSinceSweep = /* @__PURE__ */ new Map();
|
|
56811
|
+
/** Of those, the ones the tracker reports actually continued a track. */
|
|
56812
|
+
continuedSinceSweep = /* @__PURE__ */ new Map();
|
|
56623
56813
|
constructor(deps) {
|
|
56624
56814
|
this.logger = deps.logger;
|
|
56625
56815
|
this.ledger = new DurableLedger({
|
|
@@ -56703,6 +56893,7 @@ var StationaryObjectRegistry = class {
|
|
|
56703
56893
|
const entries = this.list(deviceId);
|
|
56704
56894
|
if (entries.length === 0) return {
|
|
56705
56895
|
suppressedIndices: /* @__PURE__ */ new Set(),
|
|
56896
|
+
continuableIndices: /* @__PURE__ */ new Set(),
|
|
56706
56897
|
confirmed: [],
|
|
56707
56898
|
woken: [],
|
|
56708
56899
|
reachSuppressedCount: 0
|
|
@@ -56723,14 +56914,23 @@ var StationaryObjectRegistry = class {
|
|
|
56723
56914
|
if (result.suppressedIndices.size > 0) {
|
|
56724
56915
|
this.suppressedSinceSweep.set(deviceId, (this.suppressedSinceSweep.get(deviceId) ?? 0) + result.suppressedIndices.size);
|
|
56725
56916
|
if (result.reachSuppressedCount > 0) this.reachSuppressedSinceSweep.set(deviceId, (this.reachSuppressedSinceSweep.get(deviceId) ?? 0) + result.reachSuppressedCount);
|
|
56917
|
+
if (result.continuableIndices.size > 0) this.continuableSinceSweep.set(deviceId, (this.continuableSinceSweep.get(deviceId) ?? 0) + result.continuableIndices.size);
|
|
56726
56918
|
}
|
|
56727
56919
|
return {
|
|
56728
56920
|
suppressedIndices: result.suppressedIndices,
|
|
56921
|
+
continuableIndices: result.continuableIndices,
|
|
56729
56922
|
confirmed: result.confirmed,
|
|
56730
56923
|
woken: result.woken,
|
|
56731
56924
|
reachSuppressedCount: result.reachSuppressedCount
|
|
56732
56925
|
};
|
|
56733
56926
|
}
|
|
56927
|
+
/** The tracker's answer to {@link StationaryFrameFilterResult.continuableIndices}:
|
|
56928
|
+
* how many of this frame's weak holds continued a confirmed track (D410).
|
|
56929
|
+
* Counted here so the sweep line can report it beside `continuable`. */
|
|
56930
|
+
noteContinued(deviceId, count) {
|
|
56931
|
+
if (count <= 0) return;
|
|
56932
|
+
this.continuedSinceSweep.set(deviceId, (this.continuedSinceSweep.get(deviceId) ?? 0) + count);
|
|
56933
|
+
}
|
|
56734
56934
|
/** Pending departure strikes for a device — the read-only diagnostic's whole
|
|
56735
56935
|
* reason to exist. Ordered oldest first so "what has been waiting" reads
|
|
56736
56936
|
* straight off the top. */
|
|
@@ -56869,6 +57069,8 @@ var StationaryObjectRegistry = class {
|
|
|
56869
57069
|
absorbed,
|
|
56870
57070
|
suppressed,
|
|
56871
57071
|
reach: this.reachSuppressedSinceSweep.get(deviceId) ?? 0,
|
|
57072
|
+
continuable: this.continuableSinceSweep.get(deviceId) ?? 0,
|
|
57073
|
+
continued: this.continuedSinceSweep.get(deviceId) ?? 0,
|
|
56872
57074
|
entries: this.count(deviceId)
|
|
56873
57075
|
}
|
|
56874
57076
|
});
|
|
@@ -56876,6 +57078,8 @@ var StationaryObjectRegistry = class {
|
|
|
56876
57078
|
this.absorbedSinceSweep.clear();
|
|
56877
57079
|
this.suppressedSinceSweep.clear();
|
|
56878
57080
|
this.reachSuppressedSinceSweep.clear();
|
|
57081
|
+
this.continuableSinceSweep.clear();
|
|
57082
|
+
this.continuedSinceSweep.clear();
|
|
56879
57083
|
for (const [deviceId, counters] of this.strikeCountersByDevice) this.logger.info("stationary departure strikes", {
|
|
56880
57084
|
tags: { deviceId },
|
|
56881
57085
|
meta: {
|
|
@@ -60034,6 +60238,332 @@ var IdentityStore = class {
|
|
|
60034
60238
|
}
|
|
60035
60239
|
};
|
|
60036
60240
|
//#endregion
|
|
60241
|
+
//#region src/pipeline-analytics/store/birth-decision-ledger-store.ts
|
|
60242
|
+
/**
|
|
60243
|
+
* BirthDecisionLedgerStore — the durable record of every track-birth verdict
|
|
60244
|
+
* (D409).
|
|
60245
|
+
*
|
|
60246
|
+
* A declared SQL-backed collection, like the events ops-log and the debug-note
|
|
60247
|
+
* archive next door, and for the same reason: pipeline-analytics already owns
|
|
60248
|
+
* SQLite collections, and an undeclared collection crash-loops the runner. MUST
|
|
60249
|
+
* be declared in `onInitialize` before the first write.
|
|
60250
|
+
*
|
|
60251
|
+
* Collection name: pipeline-analytics:birth-decision-ledger
|
|
60252
|
+
*
|
|
60253
|
+
* ── Why a table rather than a log line ─────────────────────────────────────
|
|
60254
|
+
* The gate ALREADY logs every decision. `logs.query` is an in-memory ring that
|
|
60255
|
+
* measured 40 000 entries covering 38 MINUTES of fleet traffic on 2026-09-08 —
|
|
60256
|
+
* so a fourteenth field on that line answers nothing the next morning, and the
|
|
60257
|
+
* questions this data exists for ("what fraction of births on 617 were decided
|
|
60258
|
+
* on a coasted frame, over the week the operator complained about") are all
|
|
60259
|
+
* multi-day. A previous investigation in this repo was blocked for exactly
|
|
60260
|
+
* this.
|
|
60261
|
+
*
|
|
60262
|
+
* ── Why it is safe on the write path ──────────────────────────────────────
|
|
60263
|
+
* ONE INSERT PER BIRTH DECISION. Not per frame, not periodic, and never an
|
|
60264
|
+
* update of an existing row. Measured fleet-wide on the live hub, 2026-09-08:
|
|
60265
|
+
* 31 decided births in 38 minutes = ~49/hour = 0.014 writes/s. The write
|
|
60266
|
+
* amplification the runtime-state guard exists to prevent
|
|
60267
|
+
* (`scripts/check-runtime-state-durability.ts`: 12-19 writes/s rewriting
|
|
60268
|
+
* ~2.6-5 GB/day to maintain 161 KB) is three orders of magnitude above this and
|
|
60269
|
+
* is a different shape entirely — that one is a periodic REWRITE of a slice
|
|
60270
|
+
* whose values do not change. Nothing here advances a clock under an unchanged
|
|
60271
|
+
* value, because every row is new.
|
|
60272
|
+
*
|
|
60273
|
+
* ── Two properties, stated once ───────────────────────────────────────────
|
|
60274
|
+
* - **It is not track-owned.** A SUPPRESSED birth never had a track; a
|
|
60275
|
+
* confirmed one is expected to age out long before this row. The column is
|
|
60276
|
+
* `sourceTrackId` and not `trackId` so the ownership derivation in
|
|
60277
|
+
* `collection-classification.ts` cannot see it and cascade it away.
|
|
60278
|
+
* - **It is bounded by COUNT, not by age.** A miss rate needs a denominator
|
|
60279
|
+
* over whatever window the table holds; a quiet fleet should keep more of
|
|
60280
|
+
* that window, not less. See {@link MAX_BIRTH_DECISION_RECORDS}.
|
|
60281
|
+
*
|
|
60282
|
+
* MEASUREMENT ONLY. Nothing reads this on the frame path; nothing gates on it.
|
|
60283
|
+
*/
|
|
60284
|
+
/**
|
|
60285
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
60286
|
+
* write="one row per track-birth VERDICT — appended by the birth loop in
|
|
60287
|
+
* `processFrame` the instant the confirmation gate's verdict resolves
|
|
60288
|
+
* (confirmed / suppressed / exhausted fail-open). Per BIRTH, never per
|
|
60289
|
+
* frame: measured at ~49 rows/hour fleet-wide on 2026-09-08. Append-only,
|
|
60290
|
+
* never updated. BEST-EFFORT — it logs and swallows, because a failed
|
|
60291
|
+
* measurement must not fail the birth it measures."
|
|
60292
|
+
* retention="NOT track retention and NOT an age clock — a miss rate needs
|
|
60293
|
+
* whatever window the table holds, and an age sweep would shorten it on a
|
|
60294
|
+
* quiet fleet exactly when more history is affordable. Bounded by ROW
|
|
60295
|
+
* COUNT (MAX_BIRTH_DECISION_RECORDS, 30000, fleet-wide), oldest `at`
|
|
60296
|
+
* first, trimmed on append. ~6 MB at the ceiling."
|
|
60297
|
+
*/
|
|
60298
|
+
var BIRTH_DECISION_LEDGER_COLLECTION = "pipeline-analytics:birth-decision-ledger";
|
|
60299
|
+
var BIRTH_DECISION_LEDGER_COLUMNS = [
|
|
60300
|
+
{
|
|
60301
|
+
name: "id",
|
|
60302
|
+
type: "TEXT",
|
|
60303
|
+
primaryKey: true,
|
|
60304
|
+
notNull: true
|
|
60305
|
+
},
|
|
60306
|
+
{
|
|
60307
|
+
name: "at",
|
|
60308
|
+
type: "INTEGER",
|
|
60309
|
+
notNull: true
|
|
60310
|
+
},
|
|
60311
|
+
{
|
|
60312
|
+
name: "deviceId",
|
|
60313
|
+
type: "INTEGER",
|
|
60314
|
+
notNull: true
|
|
60315
|
+
},
|
|
60316
|
+
{
|
|
60317
|
+
name: "sourceTrackId",
|
|
60318
|
+
type: "TEXT",
|
|
60319
|
+
notNull: true
|
|
60320
|
+
},
|
|
60321
|
+
{
|
|
60322
|
+
name: "className",
|
|
60323
|
+
type: "TEXT",
|
|
60324
|
+
notNull: true
|
|
60325
|
+
},
|
|
60326
|
+
{
|
|
60327
|
+
name: "verdict",
|
|
60328
|
+
type: "TEXT",
|
|
60329
|
+
notNull: true
|
|
60330
|
+
},
|
|
60331
|
+
{
|
|
60332
|
+
name: "reason",
|
|
60333
|
+
type: "TEXT"
|
|
60334
|
+
},
|
|
60335
|
+
{
|
|
60336
|
+
name: "attempts",
|
|
60337
|
+
type: "INTEGER",
|
|
60338
|
+
notNull: true
|
|
60339
|
+
},
|
|
60340
|
+
{
|
|
60341
|
+
name: "deferredForMs",
|
|
60342
|
+
type: "INTEGER",
|
|
60343
|
+
notNull: true
|
|
60344
|
+
},
|
|
60345
|
+
{
|
|
60346
|
+
name: "decidedOnCoastedFrame",
|
|
60347
|
+
type: "BOOLEAN",
|
|
60348
|
+
notNull: true
|
|
60349
|
+
},
|
|
60350
|
+
{
|
|
60351
|
+
name: "birthEvidenceAvailable",
|
|
60352
|
+
type: "BOOLEAN",
|
|
60353
|
+
notNull: true
|
|
60354
|
+
},
|
|
60355
|
+
{
|
|
60356
|
+
name: "decidedByBirthEvidence",
|
|
60357
|
+
type: "BOOLEAN",
|
|
60358
|
+
notNull: true
|
|
60359
|
+
},
|
|
60360
|
+
{
|
|
60361
|
+
name: "bestScore",
|
|
60362
|
+
type: "REAL"
|
|
60363
|
+
},
|
|
60364
|
+
{
|
|
60365
|
+
name: "appliedMinConfidence",
|
|
60366
|
+
type: "REAL"
|
|
60367
|
+
},
|
|
60368
|
+
{
|
|
60369
|
+
name: "firstSeen",
|
|
60370
|
+
type: "INTEGER"
|
|
60371
|
+
},
|
|
60372
|
+
{
|
|
60373
|
+
name: "motionOnsetAt",
|
|
60374
|
+
type: "INTEGER"
|
|
60375
|
+
},
|
|
60376
|
+
{
|
|
60377
|
+
name: "birthLatencyMs",
|
|
60378
|
+
type: "INTEGER"
|
|
60379
|
+
},
|
|
60380
|
+
{
|
|
60381
|
+
name: "birthIndexInBurst",
|
|
60382
|
+
type: "INTEGER"
|
|
60383
|
+
}
|
|
60384
|
+
];
|
|
60385
|
+
var BIRTH_DECISION_LEDGER_INDEXES = [{
|
|
60386
|
+
name: "idx_birthdecision_at",
|
|
60387
|
+
columns: ["at"]
|
|
60388
|
+
}, {
|
|
60389
|
+
name: "idx_birthdecision_device_at",
|
|
60390
|
+
columns: ["deviceId", "at"]
|
|
60391
|
+
}];
|
|
60392
|
+
var BirthDecisionLedgerStore = class {
|
|
60393
|
+
store;
|
|
60394
|
+
logger;
|
|
60395
|
+
newId;
|
|
60396
|
+
maxRows;
|
|
60397
|
+
trimEvery;
|
|
60398
|
+
/** Rows appended since the last trim — see {@link trim}. */
|
|
60399
|
+
sinceTrim = 0;
|
|
60400
|
+
constructor(deps) {
|
|
60401
|
+
this.store = deps.store;
|
|
60402
|
+
this.logger = deps.logger;
|
|
60403
|
+
this.newId = deps.newId ?? (() => globalThis.crypto.randomUUID());
|
|
60404
|
+
this.maxRows = deps.maxRows ?? 3e4;
|
|
60405
|
+
this.trimEvery = deps.trimEvery ?? DEFAULT_TRIM_EVERY;
|
|
60406
|
+
}
|
|
60407
|
+
/** The ceiling a store built without an override enforces. */
|
|
60408
|
+
static defaultMaxRows() {
|
|
60409
|
+
return require_dist.MAX_BIRTH_DECISION_RECORDS;
|
|
60410
|
+
}
|
|
60411
|
+
static async declare(store) {
|
|
60412
|
+
await store.declareCollection.mutate({
|
|
60413
|
+
collection: BIRTH_DECISION_LEDGER_COLLECTION,
|
|
60414
|
+
columns: [...BIRTH_DECISION_LEDGER_COLUMNS],
|
|
60415
|
+
indexes: [...BIRTH_DECISION_LEDGER_INDEXES]
|
|
60416
|
+
});
|
|
60417
|
+
}
|
|
60418
|
+
/**
|
|
60419
|
+
* Append one birth decision. NEVER THROWS.
|
|
60420
|
+
*
|
|
60421
|
+
* Best-effort, unlike the debug-note archive and deliberately so: that table
|
|
60422
|
+
* holds the only copy of something a PERSON typed, and this one holds a
|
|
60423
|
+
* measurement the system takes ~49 times an hour. A birth must not fail — nor
|
|
60424
|
+
* be delayed by a retry — because bookkeeping could not be written. The
|
|
60425
|
+
* failure is logged with `tags: { deviceId }` so a camera whose rows are
|
|
60426
|
+
* silently missing is attributable rather than an unexplained hole in the
|
|
60427
|
+
* denominator.
|
|
60428
|
+
*/
|
|
60429
|
+
async append(draft) {
|
|
60430
|
+
try {
|
|
60431
|
+
const row = require_dist.BirthDecisionRecordSchema.parse({
|
|
60432
|
+
id: this.newId(),
|
|
60433
|
+
...draft
|
|
60434
|
+
});
|
|
60435
|
+
await this.store.insert.mutate({
|
|
60436
|
+
collection: BIRTH_DECISION_LEDGER_COLLECTION,
|
|
60437
|
+
record: {
|
|
60438
|
+
id: row.id,
|
|
60439
|
+
data: {
|
|
60440
|
+
at: row.at,
|
|
60441
|
+
deviceId: row.deviceId,
|
|
60442
|
+
sourceTrackId: row.sourceTrackId,
|
|
60443
|
+
className: row.className,
|
|
60444
|
+
verdict: row.verdict,
|
|
60445
|
+
reason: row.reason,
|
|
60446
|
+
attempts: row.attempts,
|
|
60447
|
+
deferredForMs: row.deferredForMs,
|
|
60448
|
+
decidedOnCoastedFrame: row.decidedOnCoastedFrame,
|
|
60449
|
+
birthEvidenceAvailable: row.birthEvidenceAvailable,
|
|
60450
|
+
decidedByBirthEvidence: row.decidedByBirthEvidence,
|
|
60451
|
+
bestScore: row.bestScore,
|
|
60452
|
+
appliedMinConfidence: row.appliedMinConfidence,
|
|
60453
|
+
firstSeen: row.firstSeen,
|
|
60454
|
+
motionOnsetAt: row.motionOnsetAt,
|
|
60455
|
+
birthLatencyMs: row.birthLatencyMs,
|
|
60456
|
+
birthIndexInBurst: row.birthIndexInBurst
|
|
60457
|
+
}
|
|
60458
|
+
}
|
|
60459
|
+
});
|
|
60460
|
+
this.sinceTrim += 1;
|
|
60461
|
+
await this.trim();
|
|
60462
|
+
} catch (err) {
|
|
60463
|
+
this.logger.warn("birth-decision ledger append failed — this decision is not counted", {
|
|
60464
|
+
tags: { deviceId: draft.deviceId },
|
|
60465
|
+
meta: {
|
|
60466
|
+
trackId: draft.sourceTrackId,
|
|
60467
|
+
verdict: draft.verdict,
|
|
60468
|
+
error: err instanceof Error ? err.message : String(err)
|
|
60469
|
+
}
|
|
60470
|
+
});
|
|
60471
|
+
}
|
|
60472
|
+
}
|
|
60473
|
+
/**
|
|
60474
|
+
* Hold the table at its ceiling, oldest first.
|
|
60475
|
+
*
|
|
60476
|
+
* NOT an age-keyed sweep and must never become one: the boundary is computed
|
|
60477
|
+
* from the POSITION of the excess rows, never from a clock. Rows sharing the
|
|
60478
|
+
* boundary timestamp go together, which at this write rate is a rounding
|
|
60479
|
+
* error against a 30 000-row ceiling.
|
|
60480
|
+
*
|
|
60481
|
+
* AMORTISED. The debug-note archive counts on every append because it writes
|
|
60482
|
+
* a few rows a week; this one writes ~49 an hour, and a `count` round trip per
|
|
60483
|
+
* birth would triple the ledger's own store traffic to answer a question whose
|
|
60484
|
+
* answer only changes on the row that crosses the ceiling. Checking once per
|
|
60485
|
+
* {@link DEFAULT_TRIM_EVERY} appends bounds the overshoot at that many rows —
|
|
60486
|
+
* 0.3% of the ceiling — for two hours' delay in enforcing it.
|
|
60487
|
+
*/
|
|
60488
|
+
async trim() {
|
|
60489
|
+
if (this.sinceTrim < this.trimEvery) return;
|
|
60490
|
+
this.sinceTrim = 0;
|
|
60491
|
+
try {
|
|
60492
|
+
const held = await this.store.count.query({ collection: BIRTH_DECISION_LEDGER_COLLECTION });
|
|
60493
|
+
const excess = held - this.maxRows;
|
|
60494
|
+
if (excess <= 0) return;
|
|
60495
|
+
const boundary = (await this.store.query.query({
|
|
60496
|
+
collection: BIRTH_DECISION_LEDGER_COLLECTION,
|
|
60497
|
+
filter: {
|
|
60498
|
+
orderBy: {
|
|
60499
|
+
field: "at",
|
|
60500
|
+
direction: "asc"
|
|
60501
|
+
},
|
|
60502
|
+
limit: excess
|
|
60503
|
+
},
|
|
60504
|
+
columns: ["at"]
|
|
60505
|
+
})).reduce((acc, r) => {
|
|
60506
|
+
const at = r.data["at"];
|
|
60507
|
+
return typeof at === "number" && at > acc ? at : acc;
|
|
60508
|
+
}, 0);
|
|
60509
|
+
if (boundary === 0) return;
|
|
60510
|
+
const { deleted } = await this.store.deleteWhere.mutate({
|
|
60511
|
+
collection: BIRTH_DECISION_LEDGER_COLLECTION,
|
|
60512
|
+
filter: { whereBetween: { at: [0, boundary] } }
|
|
60513
|
+
});
|
|
60514
|
+
this.logger.info("birth-decision ledger trimmed to its ceiling", { meta: {
|
|
60515
|
+
held,
|
|
60516
|
+
ceiling: this.maxRows,
|
|
60517
|
+
deleted
|
|
60518
|
+
} });
|
|
60519
|
+
} catch (err) {
|
|
60520
|
+
this.logger.warn("birth-decision ledger trim failed — the ledger will keep growing", { meta: {
|
|
60521
|
+
ceiling: this.maxRows,
|
|
60522
|
+
error: err instanceof Error ? err.message : String(err)
|
|
60523
|
+
} });
|
|
60524
|
+
}
|
|
60525
|
+
}
|
|
60526
|
+
/** Birth decisions, newest first, optionally scoped by camera / verdict / age. */
|
|
60527
|
+
async list(query) {
|
|
60528
|
+
const where = {};
|
|
60529
|
+
if (query.deviceId !== void 0) where["deviceId"] = query.deviceId;
|
|
60530
|
+
if (query.verdict !== void 0) where["verdict"] = query.verdict;
|
|
60531
|
+
const filter = {
|
|
60532
|
+
orderBy: {
|
|
60533
|
+
field: "at",
|
|
60534
|
+
direction: "desc"
|
|
60535
|
+
},
|
|
60536
|
+
limit: query.limit ?? 500
|
|
60537
|
+
};
|
|
60538
|
+
if (Object.keys(where).length > 0) filter["where"] = where;
|
|
60539
|
+
if (query.since !== void 0) filter["whereBetween"] = { at: [query.since, Number.MAX_SAFE_INTEGER] };
|
|
60540
|
+
const rows = await this.store.query.query({
|
|
60541
|
+
collection: BIRTH_DECISION_LEDGER_COLLECTION,
|
|
60542
|
+
filter
|
|
60543
|
+
});
|
|
60544
|
+
const out = [];
|
|
60545
|
+
for (const r of rows) {
|
|
60546
|
+
const parsed = require_dist.BirthDecisionRecordSchema.safeParse({
|
|
60547
|
+
id: r.id,
|
|
60548
|
+
...r.data
|
|
60549
|
+
});
|
|
60550
|
+
if (parsed.success) out.push(parsed.data);
|
|
60551
|
+
else this.logger.debug("birth-decision ledger: skipped a malformed row", { meta: { id: r.id } });
|
|
60552
|
+
}
|
|
60553
|
+
return out;
|
|
60554
|
+
}
|
|
60555
|
+
};
|
|
60556
|
+
/**
|
|
60557
|
+
* How many appends between ceiling checks, by default. See
|
|
60558
|
+
* {@link BirthDecisionLedgerStore.trim}.
|
|
60559
|
+
*
|
|
60560
|
+
* 100 rows is two hours of measured fleet traffic and 0.3% of the ceiling —
|
|
60561
|
+
* small enough that the table's real size never meaningfully exceeds its stated
|
|
60562
|
+
* bound, large enough that the ledger costs one `count` per two hours instead of
|
|
60563
|
+
* one per birth.
|
|
60564
|
+
*/
|
|
60565
|
+
var DEFAULT_TRIM_EVERY = 100;
|
|
60566
|
+
//#endregion
|
|
60037
60567
|
//#region src/pipeline-analytics/store/debug-note-archive-store.ts
|
|
60038
60568
|
/**
|
|
60039
60569
|
* DebugNoteArchiveStore — the durable corpus of what operators asked to be
|
|
@@ -61659,6 +62189,13 @@ var ANALYTICS_COLLECTIONS = [
|
|
|
61659
62189
|
why: "the archived operator notes (D405) — the ONE table here whose whole purpose is to outlive the track it names. `debug` never pinned a track against retention and D405 does not change that, so the track a note was written on IS expected to be evicted; deleting the note with it is the failure the archive was built to fix (25–27 notes from one sweep unreadable the same afternoon). Its column is called `sourceTrackId` and not `trackId` precisely so the ownership derivation cannot reach it, and its bound is a fleet-wide ROW COUNT trimmed on append — never an age sweep, which would delete the corpus for the one property that makes it valuable."
|
|
61660
62190
|
}
|
|
61661
62191
|
},
|
|
62192
|
+
{
|
|
62193
|
+
collection: BIRTH_DECISION_LEDGER_COLLECTION,
|
|
62194
|
+
classification: {
|
|
62195
|
+
kind: "never-orphaned",
|
|
62196
|
+
why: "the birth-decision ledger (D409) — a measurement of what the confirmation gate decided, not analytics data about a track. Most of its rows describe births that were SUPPRESSED and therefore never had a track at all, and the confirmed ones are expected to outlive theirs: a miss rate needs its denominator for as long as the table holds, and cascading rows away with their tracks would silently shorten the window while leaving the numerator behind. Its column is called `sourceTrackId` and not `trackId` precisely so the ownership derivation cannot reach it, and its bound is a fleet-wide ROW COUNT trimmed on append — never an age sweep."
|
|
62197
|
+
}
|
|
62198
|
+
},
|
|
61662
62199
|
{
|
|
61663
62200
|
collection: STATIONARY_COLLECTION,
|
|
61664
62201
|
classification: {
|
|
@@ -69435,6 +69972,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
69435
69972
|
* operator asked for never depends on this addon's own bookkeeping.
|
|
69436
69973
|
*/
|
|
69437
69974
|
debugNoteArchive = null;
|
|
69975
|
+
/** The durable birth-decision ledger (D409) — measurement, never a gate. */
|
|
69976
|
+
birthDecisionLedger = null;
|
|
69977
|
+
/** Has the "no ledger on this runner" warning already been said? Once per
|
|
69978
|
+
* process: the condition is a property of the runner, not of the birth. */
|
|
69979
|
+
birthLedgerAbsenceLogged = false;
|
|
69438
69980
|
/** Event-media relocation engine (entity-routing Phase 4). */
|
|
69439
69981
|
mediaRelocate = null;
|
|
69440
69982
|
mediaLocationStorage = null;
|
|
@@ -69873,6 +70415,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
69873
70415
|
/** Births the gate could not MEASURE, awaiting another look on a later frame. */
|
|
69874
70416
|
deferredBirths = new DeferredBirthRegistry();
|
|
69875
70417
|
/**
|
|
70418
|
+
* The last motion RISING EDGE per device — the reference instant the
|
|
70419
|
+
* birth-latency proxy is measured from (D409). Measurement only; nothing
|
|
70420
|
+
* gates on it. Fed by the two motion handlers, which are subscribed on the
|
|
70421
|
+
* SAME designated post-processing node as the frame path, so a camera whose
|
|
70422
|
+
* births land here always has its motion mirror here too.
|
|
70423
|
+
*/
|
|
70424
|
+
motionOnsets = new MotionOnsetRegistry();
|
|
70425
|
+
/**
|
|
69876
70426
|
* The pixels each deferred birth arrived with — a RESERVE per camera, evicted
|
|
69877
70427
|
* by age inside that camera's own quota (D379).
|
|
69878
70428
|
*
|
|
@@ -70427,6 +70977,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70427
70977
|
store: api.settingsStore,
|
|
70428
70978
|
logger: logger.child("debug-note-archive")
|
|
70429
70979
|
});
|
|
70980
|
+
this.birthDecisionLedger = new BirthDecisionLedgerStore({
|
|
70981
|
+
store: api.settingsStore,
|
|
70982
|
+
logger: logger.child("birth-decision-ledger")
|
|
70983
|
+
});
|
|
70430
70984
|
{
|
|
70431
70985
|
const designated = await step("postProcessingNodeState", () => this.postProcessingNodeState.get());
|
|
70432
70986
|
this.isPostProcessingNode = ownNodeId === designated;
|
|
@@ -70747,6 +71301,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70747
71301
|
RetrainAnnotationStore.declare(api.settingsStore),
|
|
70748
71302
|
OpsLogStore.declare(api.settingsStore),
|
|
70749
71303
|
DebugNoteArchiveStore.declare(api.settingsStore),
|
|
71304
|
+
BirthDecisionLedgerStore.declare(api.settingsStore),
|
|
70750
71305
|
AnalyticsLts.declare(api.settingsStore),
|
|
70751
71306
|
SceneStore.declare(api.settingsStore),
|
|
70752
71307
|
NotificationCenter.declare(api.settingsStore),
|
|
@@ -72411,6 +72966,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
72411
72966
|
this.overlaySynthesisWarnAt.delete(deviceId);
|
|
72412
72967
|
this.forgetDeviceProcessors(deviceId);
|
|
72413
72968
|
this.levelStateByDevice.delete(deviceId);
|
|
72969
|
+
this.motionOnsets.forgetDevice(deviceId);
|
|
72414
72970
|
this.audioConfirmByDevice.delete(deviceId);
|
|
72415
72971
|
this.lastTrackActivityMs.delete(deviceId);
|
|
72416
72972
|
this.motionEventSnapshots?.forgetDevice(deviceId);
|
|
@@ -73094,6 +73650,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73094
73650
|
woken: result.stationaryWoken,
|
|
73095
73651
|
timestamp: result.timestamp
|
|
73096
73652
|
});
|
|
73653
|
+
if (this.stationaryRegistry && result.stationaryContinued > 0) this.stationaryRegistry.noteContinued(deviceId, result.stationaryContinued);
|
|
73097
73654
|
const stationaryViews = this.stationaryRegistry?.listViews(deviceId) ?? [];
|
|
73098
73655
|
const stationaryAsTracked = stationaryViews.map((v) => ({
|
|
73099
73656
|
trackId: `stationary:${v.id}`,
|
|
@@ -73197,8 +73754,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73197
73754
|
if (t.birthEvidence === void 0) continue;
|
|
73198
73755
|
this.birthEvidence.remember(key, id, Buffer.from(t.birthEvidence.jpegBase64, "base64"), exhaustionNowMs);
|
|
73199
73756
|
}
|
|
73200
|
-
const
|
|
73757
|
+
const withBirthEvidence = /* @__PURE__ */ new Set();
|
|
73758
|
+
const run = await this.confirmTrackBirths(bornCandidates.map(({ id, t }) => {
|
|
73201
73759
|
const evidenceCrop = this.birthEvidence.get(key, id, exhaustionNowMs);
|
|
73760
|
+
if (evidenceCrop !== null) withBirthEvidence.add(id);
|
|
73202
73761
|
return {
|
|
73203
73762
|
trackId: id,
|
|
73204
73763
|
className: t.className,
|
|
@@ -73213,6 +73772,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73213
73772
|
frameWidth: result.frameWidth,
|
|
73214
73773
|
frameHeight: result.frameHeight
|
|
73215
73774
|
}, exhaustedIds);
|
|
73775
|
+
const outcome = run.outcome;
|
|
73216
73776
|
const gateNowMs = Date.now();
|
|
73217
73777
|
for (const { id, t } of bornCandidates) {
|
|
73218
73778
|
if (outcome.undecided.has(id)) {
|
|
@@ -73241,6 +73801,20 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73241
73801
|
} });
|
|
73242
73802
|
continue;
|
|
73243
73803
|
}
|
|
73804
|
+
this.recordBirthDecision({
|
|
73805
|
+
deviceId,
|
|
73806
|
+
trackId: id,
|
|
73807
|
+
className: t.className,
|
|
73808
|
+
confirmed: outcome.confirmed.has(id),
|
|
73809
|
+
exhaustedFallback: wasDeferred && exhaustedIds.has(id),
|
|
73810
|
+
attempts: deferredAttempts,
|
|
73811
|
+
deferredForMs,
|
|
73812
|
+
matchedThisFrame: t.matchedThisFrame,
|
|
73813
|
+
birthEvidenceAvailable: withBirthEvidence.has(id),
|
|
73814
|
+
decision: run.decisions.get(id),
|
|
73815
|
+
decidedAtMs: gateNowMs,
|
|
73816
|
+
firstSeen: this.trackStore?.peekActive(id)?.firstSeen ?? null
|
|
73817
|
+
});
|
|
73244
73818
|
if (!outcome.confirmed.has(id)) {
|
|
73245
73819
|
this.suppressedBirths.reject(key, id);
|
|
73246
73820
|
this.trackStore?.dropActive(id);
|
|
@@ -73939,10 +74513,64 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73939
74513
|
* candidate) and each failure path fails OPEN, so the synchronous frame path
|
|
73940
74514
|
* is never blocked or reordered by a slow/failed re-detection.
|
|
73941
74515
|
*/
|
|
74516
|
+
/**
|
|
74517
|
+
* Record ONE resolved birth verdict in the durable ledger (D409).
|
|
74518
|
+
*
|
|
74519
|
+
* Synchronous up to the append, which is fired and forgotten: the frame path
|
|
74520
|
+
* awaits `confirmTrackBirths` already, and a measurement must never add a
|
|
74521
|
+
* store round trip to the latency of a birth. `BirthDecisionLedgerStore.append`
|
|
74522
|
+
* never throws, so the un-awaited promise cannot reject.
|
|
74523
|
+
*
|
|
74524
|
+
* Cost, per BIRTH and not per frame: one `Map.get`, one `Set.has`, a counter
|
|
74525
|
+
* bump and one INSERT. Measured fleet-wide on the live hub 2026-09-08 at ~49
|
|
74526
|
+
* births/hour, i.e. 0.014 writes/s — three orders of magnitude below the
|
|
74527
|
+
* 12-19/s the runtime-state guard was written for, and a different shape
|
|
74528
|
+
* (append-only, never a periodic rewrite of an unchanged slice).
|
|
74529
|
+
*
|
|
74530
|
+
* A runner with no ledger (construction failed) records nothing and says so
|
|
74531
|
+
* exactly once per process, rather than per birth: a measurement that cannot
|
|
74532
|
+
* be taken must be visible, but it must not become the noisiest line in Loki.
|
|
74533
|
+
*/
|
|
74534
|
+
recordBirthDecision(input) {
|
|
74535
|
+
const ledger = this.birthDecisionLedger;
|
|
74536
|
+
if (ledger === null) {
|
|
74537
|
+
if (!this.birthLedgerAbsenceLogged) {
|
|
74538
|
+
this.birthLedgerAbsenceLogged = true;
|
|
74539
|
+
this.ctx.logger.warn("birth decisions are NOT being measured — no ledger on this runner", { tags: { deviceId: input.deviceId } });
|
|
74540
|
+
}
|
|
74541
|
+
return;
|
|
74542
|
+
}
|
|
74543
|
+
const motionOnsetAtMs = this.motionOnsets.onsetFor(input.deviceId);
|
|
74544
|
+
const birthIndexInBurst = this.motionOnsets.noteBirth(input.deviceId);
|
|
74545
|
+
const decision = input.decision;
|
|
74546
|
+
ledger.append(buildBirthDecisionRecord({
|
|
74547
|
+
deviceId: input.deviceId,
|
|
74548
|
+
trackId: input.trackId,
|
|
74549
|
+
className: input.className,
|
|
74550
|
+
confirmed: input.confirmed,
|
|
74551
|
+
exhaustedFallback: input.exhaustedFallback,
|
|
74552
|
+
attempts: input.attempts,
|
|
74553
|
+
deferredForMs: input.deferredForMs,
|
|
74554
|
+
...input.matchedThisFrame !== void 0 ? { matchedThisFrame: input.matchedThisFrame } : {},
|
|
74555
|
+
birthEvidenceAvailable: input.birthEvidenceAvailable,
|
|
74556
|
+
decidedByBirthEvidence: decision?.cropSource === "carried",
|
|
74557
|
+
reason: decision?.reason ?? null,
|
|
74558
|
+
bestScore: decision?.bestScore ?? null,
|
|
74559
|
+
appliedMinConfidence: decision?.appliedMinConfidence ?? null,
|
|
74560
|
+
decidedAtMs: input.decidedAtMs,
|
|
74561
|
+
firstSeen: input.firstSeen,
|
|
74562
|
+
motionOnsetAtMs,
|
|
74563
|
+
birthIndexInBurst
|
|
74564
|
+
}));
|
|
74565
|
+
}
|
|
73942
74566
|
async confirmTrackBirths(candidates, params, exhaustedIds = /* @__PURE__ */ new Set()) {
|
|
74567
|
+
const decisions = /* @__PURE__ */ new Map();
|
|
73943
74568
|
const allConfirmed = () => ({
|
|
73944
|
-
|
|
73945
|
-
|
|
74569
|
+
outcome: {
|
|
74570
|
+
confirmed: new Set(candidates.map((c) => c.trackId)),
|
|
74571
|
+
undecided: /* @__PURE__ */ new Set()
|
|
74572
|
+
},
|
|
74573
|
+
decisions
|
|
73946
74574
|
});
|
|
73947
74575
|
if (candidates.length === 0) return allConfirmed();
|
|
73948
74576
|
const config = await this.resolveDeviceConfirmationGateSettings(params.deviceId);
|
|
@@ -73965,101 +74593,105 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73965
74593
|
const nodeId = frameHandle.nodeId;
|
|
73966
74594
|
const candidateNativeRecovery = /* @__PURE__ */ new Map();
|
|
73967
74595
|
for (const c of candidates) if (c.nativeRecovery !== void 0) candidateNativeRecovery.set(c.trackId, c.nativeRecovery);
|
|
73968
|
-
return
|
|
73969
|
-
|
|
73970
|
-
|
|
73971
|
-
|
|
73972
|
-
|
|
73973
|
-
|
|
73974
|
-
|
|
74596
|
+
return {
|
|
74597
|
+
outcome: await confirmBirths(candidates, config, {
|
|
74598
|
+
fetchCrop: (candidate) => this.captureScheduler.request({
|
|
74599
|
+
deviceId,
|
|
74600
|
+
trackId: candidate.trackId,
|
|
74601
|
+
kind: "confirm",
|
|
74602
|
+
staleAfterMs: config.timeoutMs,
|
|
74603
|
+
exec: () => captureCrop(frameHandle, {
|
|
74604
|
+
x: candidate.bbox.x,
|
|
74605
|
+
y: candidate.bbox.y,
|
|
74606
|
+
w: candidate.bbox.w,
|
|
74607
|
+
h: candidate.bbox.h
|
|
74608
|
+
}, frameWidth, frameHeight, CONFIRMATION_CROP_PADDING, 320, deviceId)
|
|
74609
|
+
}),
|
|
74610
|
+
fetchFallbackCrop: (candidate) => {
|
|
74611
|
+
const displayCrop = this.captureDisplayCropFn;
|
|
74612
|
+
if (!displayCrop) return Promise.resolve(null);
|
|
74613
|
+
return displayCrop(frameHandle, {
|
|
74614
|
+
x: candidate.bbox.x,
|
|
74615
|
+
y: candidate.bbox.y,
|
|
74616
|
+
w: candidate.bbox.w,
|
|
74617
|
+
h: candidate.bbox.h
|
|
74618
|
+
}, frameWidth, frameHeight, CONFIRMATION_CROP_PADDING, candidate.trackId, 320);
|
|
74619
|
+
},
|
|
74620
|
+
redetect: (cropJpeg) => this.redetectCropForConfirmation(nodeId, deviceId, cropJpeg),
|
|
74621
|
+
cropRectFor: (candidate) => captureCropFrameRect({
|
|
73975
74622
|
x: candidate.bbox.x,
|
|
73976
74623
|
y: candidate.bbox.y,
|
|
73977
74624
|
w: candidate.bbox.w,
|
|
73978
74625
|
h: candidate.bbox.h
|
|
73979
|
-
}, frameWidth, frameHeight, CONFIRMATION_CROP_PADDING,
|
|
73980
|
-
|
|
73981
|
-
|
|
73982
|
-
|
|
73983
|
-
if (!displayCrop) return Promise.resolve(null);
|
|
73984
|
-
return displayCrop(frameHandle, {
|
|
74626
|
+
}, frameWidth, frameHeight, CONFIRMATION_CROP_PADDING),
|
|
74627
|
+
minConfidenceFor: (candidate) => this.phantomCells?.spawnBar({
|
|
74628
|
+
deviceId,
|
|
74629
|
+
className: candidate.className,
|
|
73985
74630
|
x: candidate.bbox.x,
|
|
73986
74631
|
y: candidate.bbox.y,
|
|
73987
|
-
|
|
73988
|
-
|
|
73989
|
-
}
|
|
73990
|
-
|
|
73991
|
-
|
|
73992
|
-
|
|
73993
|
-
|
|
73994
|
-
|
|
73995
|
-
|
|
73996
|
-
|
|
73997
|
-
|
|
73998
|
-
|
|
73999
|
-
|
|
74000
|
-
|
|
74001
|
-
|
|
74002
|
-
|
|
74003
|
-
|
|
74004
|
-
|
|
74005
|
-
|
|
74006
|
-
|
|
74007
|
-
|
|
74008
|
-
|
|
74009
|
-
|
|
74010
|
-
|
|
74011
|
-
|
|
74012
|
-
|
|
74013
|
-
|
|
74014
|
-
|
|
74015
|
-
|
|
74016
|
-
|
|
74017
|
-
|
|
74018
|
-
|
|
74019
|
-
|
|
74020
|
-
|
|
74021
|
-
|
|
74022
|
-
|
|
74023
|
-
|
|
74024
|
-
|
|
74025
|
-
|
|
74026
|
-
|
|
74027
|
-
|
|
74028
|
-
|
|
74029
|
-
|
|
74030
|
-
|
|
74031
|
-
|
|
74032
|
-
|
|
74033
|
-
|
|
74034
|
-
|
|
74035
|
-
this.ctx.logger.info("confirmation gate: birth undecided", {
|
|
74036
|
-
tags: { deviceId },
|
|
74037
|
-
meta
|
|
74038
|
-
});
|
|
74039
|
-
break;
|
|
74040
|
-
case "confirmed":
|
|
74041
|
-
this.ctx.logger.info("confirmation gate: birth confirmed", {
|
|
74632
|
+
base: config.minConfidence,
|
|
74633
|
+
now: Date.now()
|
|
74634
|
+
}).bar ?? config.minConfidence,
|
|
74635
|
+
onDecision: (decision) => {
|
|
74636
|
+
decisions.set(decision.trackId, decision);
|
|
74637
|
+
const meta = {
|
|
74638
|
+
trackId: decision.trackId,
|
|
74639
|
+
reason: decision.reason,
|
|
74640
|
+
className: decision.className,
|
|
74641
|
+
...decision.bestScore !== void 0 ? { bestScore: decision.bestScore } : {},
|
|
74642
|
+
...decision.bestIncompatibleClass !== void 0 ? {
|
|
74643
|
+
bestIncompatibleClass: decision.bestIncompatibleClass,
|
|
74644
|
+
bestIncompatibleScore: decision.bestIncompatibleScore
|
|
74645
|
+
} : {},
|
|
74646
|
+
minConfidence: config.minConfidence,
|
|
74647
|
+
...decision.cropSource !== void 0 ? { cropSource: decision.cropSource } : {},
|
|
74648
|
+
...decision.reason === "native-pass" ? {
|
|
74649
|
+
nativeRecoveryCropSidePx: candidateNativeRecovery.get(decision.trackId)?.cropSidePx,
|
|
74650
|
+
nativeRecoveryViewPx: candidateNativeRecovery.get(decision.trackId)?.viewPx
|
|
74651
|
+
} : {},
|
|
74652
|
+
...decision.appliedMinConfidence !== void 0 && decision.appliedMinConfidence > config.minConfidence ? {
|
|
74653
|
+
appliedMinConfidence: decision.appliedMinConfidence,
|
|
74654
|
+
barRaisedByPhantomCell: true
|
|
74655
|
+
} : {}
|
|
74656
|
+
};
|
|
74657
|
+
switch (decision.verdict) {
|
|
74658
|
+
case "suppressed":
|
|
74659
|
+
this.ctx.logger.info("confirmation gate: birth suppressed (false positive)", {
|
|
74660
|
+
tags: { deviceId },
|
|
74661
|
+
meta
|
|
74662
|
+
});
|
|
74663
|
+
break;
|
|
74664
|
+
case "undecided":
|
|
74665
|
+
this.ctx.logger.info("confirmation gate: birth undecided", {
|
|
74666
|
+
tags: { deviceId },
|
|
74667
|
+
meta
|
|
74668
|
+
});
|
|
74669
|
+
break;
|
|
74670
|
+
case "confirmed":
|
|
74671
|
+
this.ctx.logger.info("confirmation gate: birth confirmed", {
|
|
74672
|
+
tags: { deviceId },
|
|
74673
|
+
meta
|
|
74674
|
+
});
|
|
74675
|
+
break;
|
|
74676
|
+
}
|
|
74677
|
+
if (decision.secondary !== void 0) {
|
|
74678
|
+
this.secondarySubjects.add(this.procKey(deviceId, source), decision.secondary, decision.trackId, Date.now());
|
|
74679
|
+
this.ctx.logger.info("confirmation gate: secondary subject promoted from the crop", {
|
|
74042
74680
|
tags: { deviceId },
|
|
74043
|
-
meta
|
|
74681
|
+
meta: {
|
|
74682
|
+
trackId: decision.trackId,
|
|
74683
|
+
className: decision.className,
|
|
74684
|
+
secondaryClass: decision.secondary.className,
|
|
74685
|
+
secondaryScore: decision.secondary.score,
|
|
74686
|
+
secondaryBbox: decision.secondary.bbox,
|
|
74687
|
+
verdict: decision.verdict
|
|
74688
|
+
}
|
|
74044
74689
|
});
|
|
74045
|
-
|
|
74046
|
-
}
|
|
74047
|
-
if (decision.secondary !== void 0) {
|
|
74048
|
-
this.secondarySubjects.add(this.procKey(deviceId, source), decision.secondary, decision.trackId, Date.now());
|
|
74049
|
-
this.ctx.logger.info("confirmation gate: secondary subject promoted from the crop", {
|
|
74050
|
-
tags: { deviceId },
|
|
74051
|
-
meta: {
|
|
74052
|
-
trackId: decision.trackId,
|
|
74053
|
-
className: decision.className,
|
|
74054
|
-
secondaryClass: decision.secondary.className,
|
|
74055
|
-
secondaryScore: decision.secondary.score,
|
|
74056
|
-
secondaryBbox: decision.secondary.bbox,
|
|
74057
|
-
verdict: decision.verdict
|
|
74058
|
-
}
|
|
74059
|
-
});
|
|
74690
|
+
}
|
|
74060
74691
|
}
|
|
74061
|
-
}
|
|
74062
|
-
|
|
74692
|
+
}, exhaustedIds),
|
|
74693
|
+
decisions
|
|
74694
|
+
};
|
|
74063
74695
|
}
|
|
74064
74696
|
async resolveGlobalFaceEnabled() {
|
|
74065
74697
|
return this.faceGlobalEnabledCache.get(() => this.faceGlobalEnabledState.get());
|
|
@@ -75391,6 +76023,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75391
76023
|
detected,
|
|
75392
76024
|
atMs: timestamp
|
|
75393
76025
|
});
|
|
76026
|
+
if (transitionedOn) this.motionOnsets.noteRisingEdge(deviceId, timestamp);
|
|
75394
76027
|
this.sceneEngine?.noteMotion(deviceId, timestamp);
|
|
75395
76028
|
await this.eventStore.insertMotion(ev);
|
|
75396
76029
|
this.ctx.eventBus.emit({
|
|
@@ -75466,6 +76099,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75466
76099
|
detected,
|
|
75467
76100
|
atMs: timestamp
|
|
75468
76101
|
});
|
|
76102
|
+
if (transitionedOn) this.motionOnsets.noteRisingEdge(deviceId, timestamp);
|
|
75469
76103
|
this.sceneEngine?.noteMotion(deviceId, timestamp);
|
|
75470
76104
|
await this.eventStore.insertMotion(ev);
|
|
75471
76105
|
this.ctx.eventBus.emit({
|
|
@@ -76387,6 +77021,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
76387
77021
|
const s = this.stationarySettingsFromCache(deviceId);
|
|
76388
77022
|
if (!s.enabled) return {
|
|
76389
77023
|
suppressedIndices: /* @__PURE__ */ new Set(),
|
|
77024
|
+
continuableIndices: /* @__PURE__ */ new Set(),
|
|
76390
77025
|
confirmed: [],
|
|
76391
77026
|
woken: []
|
|
76392
77027
|
};
|
|
@@ -78425,6 +79060,18 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
78425
79060
|
return await this.debugNoteArchive?.list(input) ?? [];
|
|
78426
79061
|
}
|
|
78427
79062
|
/**
|
|
79063
|
+
* The birth-decision ledger, newest first (D409).
|
|
79064
|
+
*
|
|
79065
|
+
* A READ of a measurement table and nothing else — no join back to the
|
|
79066
|
+
* tracks, deliberately: a SUPPRESSED birth never had a track row and a
|
|
79067
|
+
* confirmed one is expected to have aged out, so a join would return exactly
|
|
79068
|
+
* the rows the ledger exists to preserve. A runner with no ledger answers
|
|
79069
|
+
* EMPTY, which is true: nothing has been measured here.
|
|
79070
|
+
*/
|
|
79071
|
+
async listBirthDecisions(input) {
|
|
79072
|
+
return await this.birthDecisionLedger?.list(input) ?? [];
|
|
79073
|
+
}
|
|
79074
|
+
/**
|
|
78428
79075
|
* Track-centric time-based retention (design §5.1). Drains every persisted
|
|
78429
79076
|
* track for the device whose `lastSeen < cutoffMs`, page by page, through the
|
|
78430
79077
|
* widened cascade — enrolled faces/plates + identity media are exempt (design
|