@camstack/addon-post-analysis 1.2.211 → 1.2.212
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-GQbzNwJB.mjs} +252 -1
- package/dist/{dist-FHSaZkf_.js → dist-IE6UOohI.js} +269 -0
- 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-utVZjlcV.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-BL9mWOe9.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +658 -90
- package/dist/pipeline-analytics/index.mjs +658 -90
- 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-IE6UOohI.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");
|
|
@@ -53432,6 +53432,95 @@ var BirthEvidenceStore = class {
|
|
|
53432
53432
|
}
|
|
53433
53433
|
};
|
|
53434
53434
|
//#endregion
|
|
53435
|
+
//#region src/pipeline-analytics/pipeline/birth-decision-record.ts
|
|
53436
|
+
/**
|
|
53437
|
+
* birth-decision-record — turn one resolved birth verdict into the row that is
|
|
53438
|
+
* kept (D409).
|
|
53439
|
+
*
|
|
53440
|
+
* PURE, and separated from the store for the usual reason: the two rules worth
|
|
53441
|
+
* getting right — what counts as the fail-open, and when the latency proxy is
|
|
53442
|
+
* allowed to report a number — are decisions, not I/O, and a decision that only
|
|
53443
|
+
* exists inside a SQLite call cannot be tested or argued with.
|
|
53444
|
+
*
|
|
53445
|
+
* MEASUREMENT ONLY. Nothing here gates a birth, changes a verdict, or feeds
|
|
53446
|
+
* back into the frame path. The caller writes the row after the verdict is
|
|
53447
|
+
* already taken.
|
|
53448
|
+
*/
|
|
53449
|
+
/**
|
|
53450
|
+
* The verdict as the ledger reports it.
|
|
53451
|
+
*
|
|
53452
|
+
* `exhausted-fallback` is a CONFIRMATION and is counted apart from one, because
|
|
53453
|
+
* a birth nobody ever looked at and a birth measured and passed are the same
|
|
53454
|
+
* row in every existing aggregate — which is what made "the gate stopped
|
|
53455
|
+
* failing open" and "the gate now suppresses everything it cannot measure"
|
|
53456
|
+
* indistinguishable. Exhaustion attached to a SUPPRESSION is meaningless
|
|
53457
|
+
* (exhaustion only ever waves a birth through) and is discarded rather than
|
|
53458
|
+
* inventing a fourth member.
|
|
53459
|
+
*/
|
|
53460
|
+
function verdictFor(input) {
|
|
53461
|
+
if (!input.confirmed) return "suppressed";
|
|
53462
|
+
return input.exhaustedFallback ? "exhausted-fallback" : "confirmed";
|
|
53463
|
+
}
|
|
53464
|
+
/**
|
|
53465
|
+
* The latency proxy, and the three ways it declines to produce a number.
|
|
53466
|
+
*
|
|
53467
|
+
* It reports `null` — never a fabricated or clamped value — when there is no
|
|
53468
|
+
* track (a suppressed birth), no motion reference on this runner, or a
|
|
53469
|
+
* reference older than {@link MAX_BIRTH_LATENCY_PROXY_MS}. That last case is
|
|
53470
|
+
* the one that matters: on a busy scene the burst never closes and the onset is
|
|
53471
|
+
* minutes old, and a minutes-long "birth latency" would be read as the
|
|
53472
|
+
* detector's fault when it is the proxy's. When the reference is refused, the
|
|
53473
|
+
* onset and the burst index go with it — reporting an index against a reference
|
|
53474
|
+
* this row does not carry would be an invitation to join them back up.
|
|
53475
|
+
*
|
|
53476
|
+
* A NEGATIVE result is returned as measured. The analyzer's pixel-count floor
|
|
53477
|
+
* can be crossed after the detector's confidence floor for a subject entering
|
|
53478
|
+
* slowly at the frame edge, and clamping that away would delete precisely the
|
|
53479
|
+
* evidence that the proxy is unreliable. See `BirthDecisionRecordSchema` for
|
|
53480
|
+
* the full error bars.
|
|
53481
|
+
*/
|
|
53482
|
+
function latencyFor(input, firstSeen) {
|
|
53483
|
+
const onset = input.motionOnsetAtMs;
|
|
53484
|
+
if (firstSeen === null || onset === null) return {
|
|
53485
|
+
motionOnsetAt: null,
|
|
53486
|
+
birthLatencyMs: null,
|
|
53487
|
+
birthIndexInBurst: null
|
|
53488
|
+
};
|
|
53489
|
+
if (firstSeen - onset > 6e4) return {
|
|
53490
|
+
motionOnsetAt: null,
|
|
53491
|
+
birthLatencyMs: null,
|
|
53492
|
+
birthIndexInBurst: null
|
|
53493
|
+
};
|
|
53494
|
+
return {
|
|
53495
|
+
motionOnsetAt: onset,
|
|
53496
|
+
birthLatencyMs: firstSeen - onset,
|
|
53497
|
+
birthIndexInBurst: input.birthIndexInBurst
|
|
53498
|
+
};
|
|
53499
|
+
}
|
|
53500
|
+
/** Build the durable row for one resolved birth decision. */
|
|
53501
|
+
function buildBirthDecisionRecord(input) {
|
|
53502
|
+
const verdict = verdictFor(input);
|
|
53503
|
+
const firstSeen = verdict === "suppressed" ? null : input.firstSeen;
|
|
53504
|
+
const latency = latencyFor(input, firstSeen);
|
|
53505
|
+
return {
|
|
53506
|
+
at: input.decidedAtMs,
|
|
53507
|
+
deviceId: input.deviceId,
|
|
53508
|
+
sourceTrackId: input.trackId,
|
|
53509
|
+
className: input.className,
|
|
53510
|
+
verdict,
|
|
53511
|
+
reason: input.reason ?? null,
|
|
53512
|
+
attempts: input.attempts,
|
|
53513
|
+
deferredForMs: input.deferredForMs,
|
|
53514
|
+
decidedOnCoastedFrame: input.matchedThisFrame === false,
|
|
53515
|
+
birthEvidenceAvailable: input.birthEvidenceAvailable,
|
|
53516
|
+
decidedByBirthEvidence: input.decidedByBirthEvidence,
|
|
53517
|
+
bestScore: input.bestScore ?? null,
|
|
53518
|
+
appliedMinConfidence: input.appliedMinConfidence ?? null,
|
|
53519
|
+
firstSeen,
|
|
53520
|
+
...latency
|
|
53521
|
+
};
|
|
53522
|
+
}
|
|
53523
|
+
//#endregion
|
|
53435
53524
|
//#region src/pipeline-analytics/pipeline/deferred-births.ts
|
|
53436
53525
|
var DeferredBirthRegistry = class {
|
|
53437
53526
|
byDevice = /* @__PURE__ */ new Map();
|
|
@@ -53506,6 +53595,44 @@ var DeferredBirthRegistry = class {
|
|
|
53506
53595
|
}
|
|
53507
53596
|
};
|
|
53508
53597
|
//#endregion
|
|
53598
|
+
//#region src/pipeline-analytics/pipeline/motion-onset-registry.ts
|
|
53599
|
+
var MotionOnsetRegistry = class {
|
|
53600
|
+
byDevice = /* @__PURE__ */ new Map();
|
|
53601
|
+
/**
|
|
53602
|
+
* Motion went off→on at `atMs`. Opens a new burst, discarding the previous
|
|
53603
|
+
* one's birth count — a new burst is a new subject population.
|
|
53604
|
+
*/
|
|
53605
|
+
noteRisingEdge(deviceId, atMs) {
|
|
53606
|
+
this.byDevice.set(deviceId, {
|
|
53607
|
+
onsetAtMs: atMs,
|
|
53608
|
+
births: 0
|
|
53609
|
+
});
|
|
53610
|
+
}
|
|
53611
|
+
/** The open burst's onset, or `null` for a device with no reference. */
|
|
53612
|
+
onsetFor(deviceId) {
|
|
53613
|
+
return this.byDevice.get(deviceId)?.onsetAtMs ?? null;
|
|
53614
|
+
}
|
|
53615
|
+
/**
|
|
53616
|
+
* Count one birth decision against this device's burst and return its 0-based
|
|
53617
|
+
* index — `null` when there is no burst to count it against.
|
|
53618
|
+
*
|
|
53619
|
+
* Index 0 is the only index at which the burst plausibly belongs to the same
|
|
53620
|
+
* subject as the birth; the ledger records the index rather than filtering
|
|
53621
|
+
* here, so the reader can see how often it is not 0.
|
|
53622
|
+
*/
|
|
53623
|
+
noteBirth(deviceId) {
|
|
53624
|
+
const burst = this.byDevice.get(deviceId);
|
|
53625
|
+
if (burst === void 0) return null;
|
|
53626
|
+
const index = burst.births;
|
|
53627
|
+
burst.births += 1;
|
|
53628
|
+
return index;
|
|
53629
|
+
}
|
|
53630
|
+
/** Drop a device's reference (device removed / pipeline reset). */
|
|
53631
|
+
forgetDevice(deviceId) {
|
|
53632
|
+
this.byDevice.delete(deviceId);
|
|
53633
|
+
}
|
|
53634
|
+
};
|
|
53635
|
+
//#endregion
|
|
53509
53636
|
//#region src/pipeline-analytics/pipeline/detail-semantic-gate.ts
|
|
53510
53637
|
/** The detail `className` whose result IS the recognition. */
|
|
53511
53638
|
var PLATE_CLASS = "plate";
|
|
@@ -60034,6 +60161,332 @@ var IdentityStore = class {
|
|
|
60034
60161
|
}
|
|
60035
60162
|
};
|
|
60036
60163
|
//#endregion
|
|
60164
|
+
//#region src/pipeline-analytics/store/birth-decision-ledger-store.ts
|
|
60165
|
+
/**
|
|
60166
|
+
* BirthDecisionLedgerStore — the durable record of every track-birth verdict
|
|
60167
|
+
* (D409).
|
|
60168
|
+
*
|
|
60169
|
+
* A declared SQL-backed collection, like the events ops-log and the debug-note
|
|
60170
|
+
* archive next door, and for the same reason: pipeline-analytics already owns
|
|
60171
|
+
* SQLite collections, and an undeclared collection crash-loops the runner. MUST
|
|
60172
|
+
* be declared in `onInitialize` before the first write.
|
|
60173
|
+
*
|
|
60174
|
+
* Collection name: pipeline-analytics:birth-decision-ledger
|
|
60175
|
+
*
|
|
60176
|
+
* ── Why a table rather than a log line ─────────────────────────────────────
|
|
60177
|
+
* The gate ALREADY logs every decision. `logs.query` is an in-memory ring that
|
|
60178
|
+
* measured 40 000 entries covering 38 MINUTES of fleet traffic on 2026-09-08 —
|
|
60179
|
+
* so a fourteenth field on that line answers nothing the next morning, and the
|
|
60180
|
+
* questions this data exists for ("what fraction of births on 617 were decided
|
|
60181
|
+
* on a coasted frame, over the week the operator complained about") are all
|
|
60182
|
+
* multi-day. A previous investigation in this repo was blocked for exactly
|
|
60183
|
+
* this.
|
|
60184
|
+
*
|
|
60185
|
+
* ── Why it is safe on the write path ──────────────────────────────────────
|
|
60186
|
+
* ONE INSERT PER BIRTH DECISION. Not per frame, not periodic, and never an
|
|
60187
|
+
* update of an existing row. Measured fleet-wide on the live hub, 2026-09-08:
|
|
60188
|
+
* 31 decided births in 38 minutes = ~49/hour = 0.014 writes/s. The write
|
|
60189
|
+
* amplification the runtime-state guard exists to prevent
|
|
60190
|
+
* (`scripts/check-runtime-state-durability.ts`: 12-19 writes/s rewriting
|
|
60191
|
+
* ~2.6-5 GB/day to maintain 161 KB) is three orders of magnitude above this and
|
|
60192
|
+
* is a different shape entirely — that one is a periodic REWRITE of a slice
|
|
60193
|
+
* whose values do not change. Nothing here advances a clock under an unchanged
|
|
60194
|
+
* value, because every row is new.
|
|
60195
|
+
*
|
|
60196
|
+
* ── Two properties, stated once ───────────────────────────────────────────
|
|
60197
|
+
* - **It is not track-owned.** A SUPPRESSED birth never had a track; a
|
|
60198
|
+
* confirmed one is expected to age out long before this row. The column is
|
|
60199
|
+
* `sourceTrackId` and not `trackId` so the ownership derivation in
|
|
60200
|
+
* `collection-classification.ts` cannot see it and cascade it away.
|
|
60201
|
+
* - **It is bounded by COUNT, not by age.** A miss rate needs a denominator
|
|
60202
|
+
* over whatever window the table holds; a quiet fleet should keep more of
|
|
60203
|
+
* that window, not less. See {@link MAX_BIRTH_DECISION_RECORDS}.
|
|
60204
|
+
*
|
|
60205
|
+
* MEASUREMENT ONLY. Nothing reads this on the frame path; nothing gates on it.
|
|
60206
|
+
*/
|
|
60207
|
+
/**
|
|
60208
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
60209
|
+
* write="one row per track-birth VERDICT — appended by the birth loop in
|
|
60210
|
+
* `processFrame` the instant the confirmation gate's verdict resolves
|
|
60211
|
+
* (confirmed / suppressed / exhausted fail-open). Per BIRTH, never per
|
|
60212
|
+
* frame: measured at ~49 rows/hour fleet-wide on 2026-09-08. Append-only,
|
|
60213
|
+
* never updated. BEST-EFFORT — it logs and swallows, because a failed
|
|
60214
|
+
* measurement must not fail the birth it measures."
|
|
60215
|
+
* retention="NOT track retention and NOT an age clock — a miss rate needs
|
|
60216
|
+
* whatever window the table holds, and an age sweep would shorten it on a
|
|
60217
|
+
* quiet fleet exactly when more history is affordable. Bounded by ROW
|
|
60218
|
+
* COUNT (MAX_BIRTH_DECISION_RECORDS, 30000, fleet-wide), oldest `at`
|
|
60219
|
+
* first, trimmed on append. ~6 MB at the ceiling."
|
|
60220
|
+
*/
|
|
60221
|
+
var BIRTH_DECISION_LEDGER_COLLECTION = "pipeline-analytics:birth-decision-ledger";
|
|
60222
|
+
var BIRTH_DECISION_LEDGER_COLUMNS = [
|
|
60223
|
+
{
|
|
60224
|
+
name: "id",
|
|
60225
|
+
type: "TEXT",
|
|
60226
|
+
primaryKey: true,
|
|
60227
|
+
notNull: true
|
|
60228
|
+
},
|
|
60229
|
+
{
|
|
60230
|
+
name: "at",
|
|
60231
|
+
type: "INTEGER",
|
|
60232
|
+
notNull: true
|
|
60233
|
+
},
|
|
60234
|
+
{
|
|
60235
|
+
name: "deviceId",
|
|
60236
|
+
type: "INTEGER",
|
|
60237
|
+
notNull: true
|
|
60238
|
+
},
|
|
60239
|
+
{
|
|
60240
|
+
name: "sourceTrackId",
|
|
60241
|
+
type: "TEXT",
|
|
60242
|
+
notNull: true
|
|
60243
|
+
},
|
|
60244
|
+
{
|
|
60245
|
+
name: "className",
|
|
60246
|
+
type: "TEXT",
|
|
60247
|
+
notNull: true
|
|
60248
|
+
},
|
|
60249
|
+
{
|
|
60250
|
+
name: "verdict",
|
|
60251
|
+
type: "TEXT",
|
|
60252
|
+
notNull: true
|
|
60253
|
+
},
|
|
60254
|
+
{
|
|
60255
|
+
name: "reason",
|
|
60256
|
+
type: "TEXT"
|
|
60257
|
+
},
|
|
60258
|
+
{
|
|
60259
|
+
name: "attempts",
|
|
60260
|
+
type: "INTEGER",
|
|
60261
|
+
notNull: true
|
|
60262
|
+
},
|
|
60263
|
+
{
|
|
60264
|
+
name: "deferredForMs",
|
|
60265
|
+
type: "INTEGER",
|
|
60266
|
+
notNull: true
|
|
60267
|
+
},
|
|
60268
|
+
{
|
|
60269
|
+
name: "decidedOnCoastedFrame",
|
|
60270
|
+
type: "BOOLEAN",
|
|
60271
|
+
notNull: true
|
|
60272
|
+
},
|
|
60273
|
+
{
|
|
60274
|
+
name: "birthEvidenceAvailable",
|
|
60275
|
+
type: "BOOLEAN",
|
|
60276
|
+
notNull: true
|
|
60277
|
+
},
|
|
60278
|
+
{
|
|
60279
|
+
name: "decidedByBirthEvidence",
|
|
60280
|
+
type: "BOOLEAN",
|
|
60281
|
+
notNull: true
|
|
60282
|
+
},
|
|
60283
|
+
{
|
|
60284
|
+
name: "bestScore",
|
|
60285
|
+
type: "REAL"
|
|
60286
|
+
},
|
|
60287
|
+
{
|
|
60288
|
+
name: "appliedMinConfidence",
|
|
60289
|
+
type: "REAL"
|
|
60290
|
+
},
|
|
60291
|
+
{
|
|
60292
|
+
name: "firstSeen",
|
|
60293
|
+
type: "INTEGER"
|
|
60294
|
+
},
|
|
60295
|
+
{
|
|
60296
|
+
name: "motionOnsetAt",
|
|
60297
|
+
type: "INTEGER"
|
|
60298
|
+
},
|
|
60299
|
+
{
|
|
60300
|
+
name: "birthLatencyMs",
|
|
60301
|
+
type: "INTEGER"
|
|
60302
|
+
},
|
|
60303
|
+
{
|
|
60304
|
+
name: "birthIndexInBurst",
|
|
60305
|
+
type: "INTEGER"
|
|
60306
|
+
}
|
|
60307
|
+
];
|
|
60308
|
+
var BIRTH_DECISION_LEDGER_INDEXES = [{
|
|
60309
|
+
name: "idx_birthdecision_at",
|
|
60310
|
+
columns: ["at"]
|
|
60311
|
+
}, {
|
|
60312
|
+
name: "idx_birthdecision_device_at",
|
|
60313
|
+
columns: ["deviceId", "at"]
|
|
60314
|
+
}];
|
|
60315
|
+
var BirthDecisionLedgerStore = class {
|
|
60316
|
+
store;
|
|
60317
|
+
logger;
|
|
60318
|
+
newId;
|
|
60319
|
+
maxRows;
|
|
60320
|
+
trimEvery;
|
|
60321
|
+
/** Rows appended since the last trim — see {@link trim}. */
|
|
60322
|
+
sinceTrim = 0;
|
|
60323
|
+
constructor(deps) {
|
|
60324
|
+
this.store = deps.store;
|
|
60325
|
+
this.logger = deps.logger;
|
|
60326
|
+
this.newId = deps.newId ?? (() => globalThis.crypto.randomUUID());
|
|
60327
|
+
this.maxRows = deps.maxRows ?? 3e4;
|
|
60328
|
+
this.trimEvery = deps.trimEvery ?? DEFAULT_TRIM_EVERY;
|
|
60329
|
+
}
|
|
60330
|
+
/** The ceiling a store built without an override enforces. */
|
|
60331
|
+
static defaultMaxRows() {
|
|
60332
|
+
return require_dist.MAX_BIRTH_DECISION_RECORDS;
|
|
60333
|
+
}
|
|
60334
|
+
static async declare(store) {
|
|
60335
|
+
await store.declareCollection.mutate({
|
|
60336
|
+
collection: BIRTH_DECISION_LEDGER_COLLECTION,
|
|
60337
|
+
columns: [...BIRTH_DECISION_LEDGER_COLUMNS],
|
|
60338
|
+
indexes: [...BIRTH_DECISION_LEDGER_INDEXES]
|
|
60339
|
+
});
|
|
60340
|
+
}
|
|
60341
|
+
/**
|
|
60342
|
+
* Append one birth decision. NEVER THROWS.
|
|
60343
|
+
*
|
|
60344
|
+
* Best-effort, unlike the debug-note archive and deliberately so: that table
|
|
60345
|
+
* holds the only copy of something a PERSON typed, and this one holds a
|
|
60346
|
+
* measurement the system takes ~49 times an hour. A birth must not fail — nor
|
|
60347
|
+
* be delayed by a retry — because bookkeeping could not be written. The
|
|
60348
|
+
* failure is logged with `tags: { deviceId }` so a camera whose rows are
|
|
60349
|
+
* silently missing is attributable rather than an unexplained hole in the
|
|
60350
|
+
* denominator.
|
|
60351
|
+
*/
|
|
60352
|
+
async append(draft) {
|
|
60353
|
+
try {
|
|
60354
|
+
const row = require_dist.BirthDecisionRecordSchema.parse({
|
|
60355
|
+
id: this.newId(),
|
|
60356
|
+
...draft
|
|
60357
|
+
});
|
|
60358
|
+
await this.store.insert.mutate({
|
|
60359
|
+
collection: BIRTH_DECISION_LEDGER_COLLECTION,
|
|
60360
|
+
record: {
|
|
60361
|
+
id: row.id,
|
|
60362
|
+
data: {
|
|
60363
|
+
at: row.at,
|
|
60364
|
+
deviceId: row.deviceId,
|
|
60365
|
+
sourceTrackId: row.sourceTrackId,
|
|
60366
|
+
className: row.className,
|
|
60367
|
+
verdict: row.verdict,
|
|
60368
|
+
reason: row.reason,
|
|
60369
|
+
attempts: row.attempts,
|
|
60370
|
+
deferredForMs: row.deferredForMs,
|
|
60371
|
+
decidedOnCoastedFrame: row.decidedOnCoastedFrame,
|
|
60372
|
+
birthEvidenceAvailable: row.birthEvidenceAvailable,
|
|
60373
|
+
decidedByBirthEvidence: row.decidedByBirthEvidence,
|
|
60374
|
+
bestScore: row.bestScore,
|
|
60375
|
+
appliedMinConfidence: row.appliedMinConfidence,
|
|
60376
|
+
firstSeen: row.firstSeen,
|
|
60377
|
+
motionOnsetAt: row.motionOnsetAt,
|
|
60378
|
+
birthLatencyMs: row.birthLatencyMs,
|
|
60379
|
+
birthIndexInBurst: row.birthIndexInBurst
|
|
60380
|
+
}
|
|
60381
|
+
}
|
|
60382
|
+
});
|
|
60383
|
+
this.sinceTrim += 1;
|
|
60384
|
+
await this.trim();
|
|
60385
|
+
} catch (err) {
|
|
60386
|
+
this.logger.warn("birth-decision ledger append failed — this decision is not counted", {
|
|
60387
|
+
tags: { deviceId: draft.deviceId },
|
|
60388
|
+
meta: {
|
|
60389
|
+
trackId: draft.sourceTrackId,
|
|
60390
|
+
verdict: draft.verdict,
|
|
60391
|
+
error: err instanceof Error ? err.message : String(err)
|
|
60392
|
+
}
|
|
60393
|
+
});
|
|
60394
|
+
}
|
|
60395
|
+
}
|
|
60396
|
+
/**
|
|
60397
|
+
* Hold the table at its ceiling, oldest first.
|
|
60398
|
+
*
|
|
60399
|
+
* NOT an age-keyed sweep and must never become one: the boundary is computed
|
|
60400
|
+
* from the POSITION of the excess rows, never from a clock. Rows sharing the
|
|
60401
|
+
* boundary timestamp go together, which at this write rate is a rounding
|
|
60402
|
+
* error against a 30 000-row ceiling.
|
|
60403
|
+
*
|
|
60404
|
+
* AMORTISED. The debug-note archive counts on every append because it writes
|
|
60405
|
+
* a few rows a week; this one writes ~49 an hour, and a `count` round trip per
|
|
60406
|
+
* birth would triple the ledger's own store traffic to answer a question whose
|
|
60407
|
+
* answer only changes on the row that crosses the ceiling. Checking once per
|
|
60408
|
+
* {@link DEFAULT_TRIM_EVERY} appends bounds the overshoot at that many rows —
|
|
60409
|
+
* 0.3% of the ceiling — for two hours' delay in enforcing it.
|
|
60410
|
+
*/
|
|
60411
|
+
async trim() {
|
|
60412
|
+
if (this.sinceTrim < this.trimEvery) return;
|
|
60413
|
+
this.sinceTrim = 0;
|
|
60414
|
+
try {
|
|
60415
|
+
const held = await this.store.count.query({ collection: BIRTH_DECISION_LEDGER_COLLECTION });
|
|
60416
|
+
const excess = held - this.maxRows;
|
|
60417
|
+
if (excess <= 0) return;
|
|
60418
|
+
const boundary = (await this.store.query.query({
|
|
60419
|
+
collection: BIRTH_DECISION_LEDGER_COLLECTION,
|
|
60420
|
+
filter: {
|
|
60421
|
+
orderBy: {
|
|
60422
|
+
field: "at",
|
|
60423
|
+
direction: "asc"
|
|
60424
|
+
},
|
|
60425
|
+
limit: excess
|
|
60426
|
+
},
|
|
60427
|
+
columns: ["at"]
|
|
60428
|
+
})).reduce((acc, r) => {
|
|
60429
|
+
const at = r.data["at"];
|
|
60430
|
+
return typeof at === "number" && at > acc ? at : acc;
|
|
60431
|
+
}, 0);
|
|
60432
|
+
if (boundary === 0) return;
|
|
60433
|
+
const { deleted } = await this.store.deleteWhere.mutate({
|
|
60434
|
+
collection: BIRTH_DECISION_LEDGER_COLLECTION,
|
|
60435
|
+
filter: { whereBetween: { at: [0, boundary] } }
|
|
60436
|
+
});
|
|
60437
|
+
this.logger.info("birth-decision ledger trimmed to its ceiling", { meta: {
|
|
60438
|
+
held,
|
|
60439
|
+
ceiling: this.maxRows,
|
|
60440
|
+
deleted
|
|
60441
|
+
} });
|
|
60442
|
+
} catch (err) {
|
|
60443
|
+
this.logger.warn("birth-decision ledger trim failed — the ledger will keep growing", { meta: {
|
|
60444
|
+
ceiling: this.maxRows,
|
|
60445
|
+
error: err instanceof Error ? err.message : String(err)
|
|
60446
|
+
} });
|
|
60447
|
+
}
|
|
60448
|
+
}
|
|
60449
|
+
/** Birth decisions, newest first, optionally scoped by camera / verdict / age. */
|
|
60450
|
+
async list(query) {
|
|
60451
|
+
const where = {};
|
|
60452
|
+
if (query.deviceId !== void 0) where["deviceId"] = query.deviceId;
|
|
60453
|
+
if (query.verdict !== void 0) where["verdict"] = query.verdict;
|
|
60454
|
+
const filter = {
|
|
60455
|
+
orderBy: {
|
|
60456
|
+
field: "at",
|
|
60457
|
+
direction: "desc"
|
|
60458
|
+
},
|
|
60459
|
+
limit: query.limit ?? 500
|
|
60460
|
+
};
|
|
60461
|
+
if (Object.keys(where).length > 0) filter["where"] = where;
|
|
60462
|
+
if (query.since !== void 0) filter["whereBetween"] = { at: [query.since, Number.MAX_SAFE_INTEGER] };
|
|
60463
|
+
const rows = await this.store.query.query({
|
|
60464
|
+
collection: BIRTH_DECISION_LEDGER_COLLECTION,
|
|
60465
|
+
filter
|
|
60466
|
+
});
|
|
60467
|
+
const out = [];
|
|
60468
|
+
for (const r of rows) {
|
|
60469
|
+
const parsed = require_dist.BirthDecisionRecordSchema.safeParse({
|
|
60470
|
+
id: r.id,
|
|
60471
|
+
...r.data
|
|
60472
|
+
});
|
|
60473
|
+
if (parsed.success) out.push(parsed.data);
|
|
60474
|
+
else this.logger.debug("birth-decision ledger: skipped a malformed row", { meta: { id: r.id } });
|
|
60475
|
+
}
|
|
60476
|
+
return out;
|
|
60477
|
+
}
|
|
60478
|
+
};
|
|
60479
|
+
/**
|
|
60480
|
+
* How many appends between ceiling checks, by default. See
|
|
60481
|
+
* {@link BirthDecisionLedgerStore.trim}.
|
|
60482
|
+
*
|
|
60483
|
+
* 100 rows is two hours of measured fleet traffic and 0.3% of the ceiling —
|
|
60484
|
+
* small enough that the table's real size never meaningfully exceeds its stated
|
|
60485
|
+
* bound, large enough that the ledger costs one `count` per two hours instead of
|
|
60486
|
+
* one per birth.
|
|
60487
|
+
*/
|
|
60488
|
+
var DEFAULT_TRIM_EVERY = 100;
|
|
60489
|
+
//#endregion
|
|
60037
60490
|
//#region src/pipeline-analytics/store/debug-note-archive-store.ts
|
|
60038
60491
|
/**
|
|
60039
60492
|
* DebugNoteArchiveStore — the durable corpus of what operators asked to be
|
|
@@ -61659,6 +62112,13 @@ var ANALYTICS_COLLECTIONS = [
|
|
|
61659
62112
|
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
62113
|
}
|
|
61661
62114
|
},
|
|
62115
|
+
{
|
|
62116
|
+
collection: BIRTH_DECISION_LEDGER_COLLECTION,
|
|
62117
|
+
classification: {
|
|
62118
|
+
kind: "never-orphaned",
|
|
62119
|
+
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."
|
|
62120
|
+
}
|
|
62121
|
+
},
|
|
61662
62122
|
{
|
|
61663
62123
|
collection: STATIONARY_COLLECTION,
|
|
61664
62124
|
classification: {
|
|
@@ -69435,6 +69895,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
69435
69895
|
* operator asked for never depends on this addon's own bookkeeping.
|
|
69436
69896
|
*/
|
|
69437
69897
|
debugNoteArchive = null;
|
|
69898
|
+
/** The durable birth-decision ledger (D409) — measurement, never a gate. */
|
|
69899
|
+
birthDecisionLedger = null;
|
|
69900
|
+
/** Has the "no ledger on this runner" warning already been said? Once per
|
|
69901
|
+
* process: the condition is a property of the runner, not of the birth. */
|
|
69902
|
+
birthLedgerAbsenceLogged = false;
|
|
69438
69903
|
/** Event-media relocation engine (entity-routing Phase 4). */
|
|
69439
69904
|
mediaRelocate = null;
|
|
69440
69905
|
mediaLocationStorage = null;
|
|
@@ -69873,6 +70338,14 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
69873
70338
|
/** Births the gate could not MEASURE, awaiting another look on a later frame. */
|
|
69874
70339
|
deferredBirths = new DeferredBirthRegistry();
|
|
69875
70340
|
/**
|
|
70341
|
+
* The last motion RISING EDGE per device — the reference instant the
|
|
70342
|
+
* birth-latency proxy is measured from (D409). Measurement only; nothing
|
|
70343
|
+
* gates on it. Fed by the two motion handlers, which are subscribed on the
|
|
70344
|
+
* SAME designated post-processing node as the frame path, so a camera whose
|
|
70345
|
+
* births land here always has its motion mirror here too.
|
|
70346
|
+
*/
|
|
70347
|
+
motionOnsets = new MotionOnsetRegistry();
|
|
70348
|
+
/**
|
|
69876
70349
|
* The pixels each deferred birth arrived with — a RESERVE per camera, evicted
|
|
69877
70350
|
* by age inside that camera's own quota (D379).
|
|
69878
70351
|
*
|
|
@@ -70427,6 +70900,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70427
70900
|
store: api.settingsStore,
|
|
70428
70901
|
logger: logger.child("debug-note-archive")
|
|
70429
70902
|
});
|
|
70903
|
+
this.birthDecisionLedger = new BirthDecisionLedgerStore({
|
|
70904
|
+
store: api.settingsStore,
|
|
70905
|
+
logger: logger.child("birth-decision-ledger")
|
|
70906
|
+
});
|
|
70430
70907
|
{
|
|
70431
70908
|
const designated = await step("postProcessingNodeState", () => this.postProcessingNodeState.get());
|
|
70432
70909
|
this.isPostProcessingNode = ownNodeId === designated;
|
|
@@ -70747,6 +71224,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70747
71224
|
RetrainAnnotationStore.declare(api.settingsStore),
|
|
70748
71225
|
OpsLogStore.declare(api.settingsStore),
|
|
70749
71226
|
DebugNoteArchiveStore.declare(api.settingsStore),
|
|
71227
|
+
BirthDecisionLedgerStore.declare(api.settingsStore),
|
|
70750
71228
|
AnalyticsLts.declare(api.settingsStore),
|
|
70751
71229
|
SceneStore.declare(api.settingsStore),
|
|
70752
71230
|
NotificationCenter.declare(api.settingsStore),
|
|
@@ -72411,6 +72889,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
72411
72889
|
this.overlaySynthesisWarnAt.delete(deviceId);
|
|
72412
72890
|
this.forgetDeviceProcessors(deviceId);
|
|
72413
72891
|
this.levelStateByDevice.delete(deviceId);
|
|
72892
|
+
this.motionOnsets.forgetDevice(deviceId);
|
|
72414
72893
|
this.audioConfirmByDevice.delete(deviceId);
|
|
72415
72894
|
this.lastTrackActivityMs.delete(deviceId);
|
|
72416
72895
|
this.motionEventSnapshots?.forgetDevice(deviceId);
|
|
@@ -73197,8 +73676,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73197
73676
|
if (t.birthEvidence === void 0) continue;
|
|
73198
73677
|
this.birthEvidence.remember(key, id, Buffer.from(t.birthEvidence.jpegBase64, "base64"), exhaustionNowMs);
|
|
73199
73678
|
}
|
|
73200
|
-
const
|
|
73679
|
+
const withBirthEvidence = /* @__PURE__ */ new Set();
|
|
73680
|
+
const run = await this.confirmTrackBirths(bornCandidates.map(({ id, t }) => {
|
|
73201
73681
|
const evidenceCrop = this.birthEvidence.get(key, id, exhaustionNowMs);
|
|
73682
|
+
if (evidenceCrop !== null) withBirthEvidence.add(id);
|
|
73202
73683
|
return {
|
|
73203
73684
|
trackId: id,
|
|
73204
73685
|
className: t.className,
|
|
@@ -73213,6 +73694,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73213
73694
|
frameWidth: result.frameWidth,
|
|
73214
73695
|
frameHeight: result.frameHeight
|
|
73215
73696
|
}, exhaustedIds);
|
|
73697
|
+
const outcome = run.outcome;
|
|
73216
73698
|
const gateNowMs = Date.now();
|
|
73217
73699
|
for (const { id, t } of bornCandidates) {
|
|
73218
73700
|
if (outcome.undecided.has(id)) {
|
|
@@ -73241,6 +73723,20 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73241
73723
|
} });
|
|
73242
73724
|
continue;
|
|
73243
73725
|
}
|
|
73726
|
+
this.recordBirthDecision({
|
|
73727
|
+
deviceId,
|
|
73728
|
+
trackId: id,
|
|
73729
|
+
className: t.className,
|
|
73730
|
+
confirmed: outcome.confirmed.has(id),
|
|
73731
|
+
exhaustedFallback: wasDeferred && exhaustedIds.has(id),
|
|
73732
|
+
attempts: deferredAttempts,
|
|
73733
|
+
deferredForMs,
|
|
73734
|
+
matchedThisFrame: t.matchedThisFrame,
|
|
73735
|
+
birthEvidenceAvailable: withBirthEvidence.has(id),
|
|
73736
|
+
decision: run.decisions.get(id),
|
|
73737
|
+
decidedAtMs: gateNowMs,
|
|
73738
|
+
firstSeen: this.trackStore?.peekActive(id)?.firstSeen ?? null
|
|
73739
|
+
});
|
|
73244
73740
|
if (!outcome.confirmed.has(id)) {
|
|
73245
73741
|
this.suppressedBirths.reject(key, id);
|
|
73246
73742
|
this.trackStore?.dropActive(id);
|
|
@@ -73939,10 +74435,64 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73939
74435
|
* candidate) and each failure path fails OPEN, so the synchronous frame path
|
|
73940
74436
|
* is never blocked or reordered by a slow/failed re-detection.
|
|
73941
74437
|
*/
|
|
74438
|
+
/**
|
|
74439
|
+
* Record ONE resolved birth verdict in the durable ledger (D409).
|
|
74440
|
+
*
|
|
74441
|
+
* Synchronous up to the append, which is fired and forgotten: the frame path
|
|
74442
|
+
* awaits `confirmTrackBirths` already, and a measurement must never add a
|
|
74443
|
+
* store round trip to the latency of a birth. `BirthDecisionLedgerStore.append`
|
|
74444
|
+
* never throws, so the un-awaited promise cannot reject.
|
|
74445
|
+
*
|
|
74446
|
+
* Cost, per BIRTH and not per frame: one `Map.get`, one `Set.has`, a counter
|
|
74447
|
+
* bump and one INSERT. Measured fleet-wide on the live hub 2026-09-08 at ~49
|
|
74448
|
+
* births/hour, i.e. 0.014 writes/s — three orders of magnitude below the
|
|
74449
|
+
* 12-19/s the runtime-state guard was written for, and a different shape
|
|
74450
|
+
* (append-only, never a periodic rewrite of an unchanged slice).
|
|
74451
|
+
*
|
|
74452
|
+
* A runner with no ledger (construction failed) records nothing and says so
|
|
74453
|
+
* exactly once per process, rather than per birth: a measurement that cannot
|
|
74454
|
+
* be taken must be visible, but it must not become the noisiest line in Loki.
|
|
74455
|
+
*/
|
|
74456
|
+
recordBirthDecision(input) {
|
|
74457
|
+
const ledger = this.birthDecisionLedger;
|
|
74458
|
+
if (ledger === null) {
|
|
74459
|
+
if (!this.birthLedgerAbsenceLogged) {
|
|
74460
|
+
this.birthLedgerAbsenceLogged = true;
|
|
74461
|
+
this.ctx.logger.warn("birth decisions are NOT being measured — no ledger on this runner", { tags: { deviceId: input.deviceId } });
|
|
74462
|
+
}
|
|
74463
|
+
return;
|
|
74464
|
+
}
|
|
74465
|
+
const motionOnsetAtMs = this.motionOnsets.onsetFor(input.deviceId);
|
|
74466
|
+
const birthIndexInBurst = this.motionOnsets.noteBirth(input.deviceId);
|
|
74467
|
+
const decision = input.decision;
|
|
74468
|
+
ledger.append(buildBirthDecisionRecord({
|
|
74469
|
+
deviceId: input.deviceId,
|
|
74470
|
+
trackId: input.trackId,
|
|
74471
|
+
className: input.className,
|
|
74472
|
+
confirmed: input.confirmed,
|
|
74473
|
+
exhaustedFallback: input.exhaustedFallback,
|
|
74474
|
+
attempts: input.attempts,
|
|
74475
|
+
deferredForMs: input.deferredForMs,
|
|
74476
|
+
...input.matchedThisFrame !== void 0 ? { matchedThisFrame: input.matchedThisFrame } : {},
|
|
74477
|
+
birthEvidenceAvailable: input.birthEvidenceAvailable,
|
|
74478
|
+
decidedByBirthEvidence: decision?.cropSource === "carried",
|
|
74479
|
+
reason: decision?.reason ?? null,
|
|
74480
|
+
bestScore: decision?.bestScore ?? null,
|
|
74481
|
+
appliedMinConfidence: decision?.appliedMinConfidence ?? null,
|
|
74482
|
+
decidedAtMs: input.decidedAtMs,
|
|
74483
|
+
firstSeen: input.firstSeen,
|
|
74484
|
+
motionOnsetAtMs,
|
|
74485
|
+
birthIndexInBurst
|
|
74486
|
+
}));
|
|
74487
|
+
}
|
|
73942
74488
|
async confirmTrackBirths(candidates, params, exhaustedIds = /* @__PURE__ */ new Set()) {
|
|
74489
|
+
const decisions = /* @__PURE__ */ new Map();
|
|
73943
74490
|
const allConfirmed = () => ({
|
|
73944
|
-
|
|
73945
|
-
|
|
74491
|
+
outcome: {
|
|
74492
|
+
confirmed: new Set(candidates.map((c) => c.trackId)),
|
|
74493
|
+
undecided: /* @__PURE__ */ new Set()
|
|
74494
|
+
},
|
|
74495
|
+
decisions
|
|
73946
74496
|
});
|
|
73947
74497
|
if (candidates.length === 0) return allConfirmed();
|
|
73948
74498
|
const config = await this.resolveDeviceConfirmationGateSettings(params.deviceId);
|
|
@@ -73965,101 +74515,105 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73965
74515
|
const nodeId = frameHandle.nodeId;
|
|
73966
74516
|
const candidateNativeRecovery = /* @__PURE__ */ new Map();
|
|
73967
74517
|
for (const c of candidates) if (c.nativeRecovery !== void 0) candidateNativeRecovery.set(c.trackId, c.nativeRecovery);
|
|
73968
|
-
return
|
|
73969
|
-
|
|
73970
|
-
|
|
73971
|
-
|
|
73972
|
-
|
|
73973
|
-
|
|
73974
|
-
|
|
74518
|
+
return {
|
|
74519
|
+
outcome: await confirmBirths(candidates, config, {
|
|
74520
|
+
fetchCrop: (candidate) => this.captureScheduler.request({
|
|
74521
|
+
deviceId,
|
|
74522
|
+
trackId: candidate.trackId,
|
|
74523
|
+
kind: "confirm",
|
|
74524
|
+
staleAfterMs: config.timeoutMs,
|
|
74525
|
+
exec: () => captureCrop(frameHandle, {
|
|
74526
|
+
x: candidate.bbox.x,
|
|
74527
|
+
y: candidate.bbox.y,
|
|
74528
|
+
w: candidate.bbox.w,
|
|
74529
|
+
h: candidate.bbox.h
|
|
74530
|
+
}, frameWidth, frameHeight, CONFIRMATION_CROP_PADDING, 320, deviceId)
|
|
74531
|
+
}),
|
|
74532
|
+
fetchFallbackCrop: (candidate) => {
|
|
74533
|
+
const displayCrop = this.captureDisplayCropFn;
|
|
74534
|
+
if (!displayCrop) return Promise.resolve(null);
|
|
74535
|
+
return displayCrop(frameHandle, {
|
|
74536
|
+
x: candidate.bbox.x,
|
|
74537
|
+
y: candidate.bbox.y,
|
|
74538
|
+
w: candidate.bbox.w,
|
|
74539
|
+
h: candidate.bbox.h
|
|
74540
|
+
}, frameWidth, frameHeight, CONFIRMATION_CROP_PADDING, candidate.trackId, 320);
|
|
74541
|
+
},
|
|
74542
|
+
redetect: (cropJpeg) => this.redetectCropForConfirmation(nodeId, deviceId, cropJpeg),
|
|
74543
|
+
cropRectFor: (candidate) => captureCropFrameRect({
|
|
73975
74544
|
x: candidate.bbox.x,
|
|
73976
74545
|
y: candidate.bbox.y,
|
|
73977
74546
|
w: candidate.bbox.w,
|
|
73978
74547
|
h: candidate.bbox.h
|
|
73979
|
-
}, frameWidth, frameHeight, CONFIRMATION_CROP_PADDING,
|
|
73980
|
-
|
|
73981
|
-
|
|
73982
|
-
|
|
73983
|
-
if (!displayCrop) return Promise.resolve(null);
|
|
73984
|
-
return displayCrop(frameHandle, {
|
|
74548
|
+
}, frameWidth, frameHeight, CONFIRMATION_CROP_PADDING),
|
|
74549
|
+
minConfidenceFor: (candidate) => this.phantomCells?.spawnBar({
|
|
74550
|
+
deviceId,
|
|
74551
|
+
className: candidate.className,
|
|
73985
74552
|
x: candidate.bbox.x,
|
|
73986
74553
|
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", {
|
|
74554
|
+
base: config.minConfidence,
|
|
74555
|
+
now: Date.now()
|
|
74556
|
+
}).bar ?? config.minConfidence,
|
|
74557
|
+
onDecision: (decision) => {
|
|
74558
|
+
decisions.set(decision.trackId, decision);
|
|
74559
|
+
const meta = {
|
|
74560
|
+
trackId: decision.trackId,
|
|
74561
|
+
reason: decision.reason,
|
|
74562
|
+
className: decision.className,
|
|
74563
|
+
...decision.bestScore !== void 0 ? { bestScore: decision.bestScore } : {},
|
|
74564
|
+
...decision.bestIncompatibleClass !== void 0 ? {
|
|
74565
|
+
bestIncompatibleClass: decision.bestIncompatibleClass,
|
|
74566
|
+
bestIncompatibleScore: decision.bestIncompatibleScore
|
|
74567
|
+
} : {},
|
|
74568
|
+
minConfidence: config.minConfidence,
|
|
74569
|
+
...decision.cropSource !== void 0 ? { cropSource: decision.cropSource } : {},
|
|
74570
|
+
...decision.reason === "native-pass" ? {
|
|
74571
|
+
nativeRecoveryCropSidePx: candidateNativeRecovery.get(decision.trackId)?.cropSidePx,
|
|
74572
|
+
nativeRecoveryViewPx: candidateNativeRecovery.get(decision.trackId)?.viewPx
|
|
74573
|
+
} : {},
|
|
74574
|
+
...decision.appliedMinConfidence !== void 0 && decision.appliedMinConfidence > config.minConfidence ? {
|
|
74575
|
+
appliedMinConfidence: decision.appliedMinConfidence,
|
|
74576
|
+
barRaisedByPhantomCell: true
|
|
74577
|
+
} : {}
|
|
74578
|
+
};
|
|
74579
|
+
switch (decision.verdict) {
|
|
74580
|
+
case "suppressed":
|
|
74581
|
+
this.ctx.logger.info("confirmation gate: birth suppressed (false positive)", {
|
|
74582
|
+
tags: { deviceId },
|
|
74583
|
+
meta
|
|
74584
|
+
});
|
|
74585
|
+
break;
|
|
74586
|
+
case "undecided":
|
|
74587
|
+
this.ctx.logger.info("confirmation gate: birth undecided", {
|
|
74588
|
+
tags: { deviceId },
|
|
74589
|
+
meta
|
|
74590
|
+
});
|
|
74591
|
+
break;
|
|
74592
|
+
case "confirmed":
|
|
74593
|
+
this.ctx.logger.info("confirmation gate: birth confirmed", {
|
|
74594
|
+
tags: { deviceId },
|
|
74595
|
+
meta
|
|
74596
|
+
});
|
|
74597
|
+
break;
|
|
74598
|
+
}
|
|
74599
|
+
if (decision.secondary !== void 0) {
|
|
74600
|
+
this.secondarySubjects.add(this.procKey(deviceId, source), decision.secondary, decision.trackId, Date.now());
|
|
74601
|
+
this.ctx.logger.info("confirmation gate: secondary subject promoted from the crop", {
|
|
74042
74602
|
tags: { deviceId },
|
|
74043
|
-
meta
|
|
74603
|
+
meta: {
|
|
74604
|
+
trackId: decision.trackId,
|
|
74605
|
+
className: decision.className,
|
|
74606
|
+
secondaryClass: decision.secondary.className,
|
|
74607
|
+
secondaryScore: decision.secondary.score,
|
|
74608
|
+
secondaryBbox: decision.secondary.bbox,
|
|
74609
|
+
verdict: decision.verdict
|
|
74610
|
+
}
|
|
74044
74611
|
});
|
|
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
|
-
});
|
|
74612
|
+
}
|
|
74060
74613
|
}
|
|
74061
|
-
}
|
|
74062
|
-
|
|
74614
|
+
}, exhaustedIds),
|
|
74615
|
+
decisions
|
|
74616
|
+
};
|
|
74063
74617
|
}
|
|
74064
74618
|
async resolveGlobalFaceEnabled() {
|
|
74065
74619
|
return this.faceGlobalEnabledCache.get(() => this.faceGlobalEnabledState.get());
|
|
@@ -75391,6 +75945,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75391
75945
|
detected,
|
|
75392
75946
|
atMs: timestamp
|
|
75393
75947
|
});
|
|
75948
|
+
if (transitionedOn) this.motionOnsets.noteRisingEdge(deviceId, timestamp);
|
|
75394
75949
|
this.sceneEngine?.noteMotion(deviceId, timestamp);
|
|
75395
75950
|
await this.eventStore.insertMotion(ev);
|
|
75396
75951
|
this.ctx.eventBus.emit({
|
|
@@ -75466,6 +76021,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75466
76021
|
detected,
|
|
75467
76022
|
atMs: timestamp
|
|
75468
76023
|
});
|
|
76024
|
+
if (transitionedOn) this.motionOnsets.noteRisingEdge(deviceId, timestamp);
|
|
75469
76025
|
this.sceneEngine?.noteMotion(deviceId, timestamp);
|
|
75470
76026
|
await this.eventStore.insertMotion(ev);
|
|
75471
76027
|
this.ctx.eventBus.emit({
|
|
@@ -78425,6 +78981,18 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
78425
78981
|
return await this.debugNoteArchive?.list(input) ?? [];
|
|
78426
78982
|
}
|
|
78427
78983
|
/**
|
|
78984
|
+
* The birth-decision ledger, newest first (D409).
|
|
78985
|
+
*
|
|
78986
|
+
* A READ of a measurement table and nothing else — no join back to the
|
|
78987
|
+
* tracks, deliberately: a SUPPRESSED birth never had a track row and a
|
|
78988
|
+
* confirmed one is expected to have aged out, so a join would return exactly
|
|
78989
|
+
* the rows the ledger exists to preserve. A runner with no ledger answers
|
|
78990
|
+
* EMPTY, which is true: nothing has been measured here.
|
|
78991
|
+
*/
|
|
78992
|
+
async listBirthDecisions(input) {
|
|
78993
|
+
return await this.birthDecisionLedger?.list(input) ?? [];
|
|
78994
|
+
}
|
|
78995
|
+
/**
|
|
78428
78996
|
* Track-centric time-based retention (design §5.1). Drains every persisted
|
|
78429
78997
|
* track for the device whose `lastSeen < cutoffMs`, page by page, through the
|
|
78430
78998
|
* widened cascade — enrolled faces/plates + identity media are exempt (design
|