@camstack/addon-post-analysis 1.2.180 → 1.2.182
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-DEAreP8H.mjs → dist-DvmLRRIq.mjs} +13 -4
- package/dist/{dist-BB70nZUm.js → dist-_fedHYU3.js} +13 -4
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Bj0usC-2.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BirG5etn.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-8m93NL8-.mjs → hostInit-DZG5yDTK.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +484 -6
- package/dist/pipeline-analytics/index.mjs +538 -60
- 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-_fedHYU3.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");
|
|
@@ -35980,6 +35980,7 @@ var EMPTY_CASCADE_TALLY = {
|
|
|
35980
35980
|
faces: 0,
|
|
35981
35981
|
plates: 0,
|
|
35982
35982
|
embeddings: 0,
|
|
35983
|
+
summaries: 0,
|
|
35983
35984
|
bytesReclaimed: 0
|
|
35984
35985
|
};
|
|
35985
35986
|
/** Fold a leaf store's receipts into a running tally. */
|
|
@@ -36023,6 +36024,13 @@ function addLeafCounts(tally, counts) {
|
|
|
36023
36024
|
bytesReclaimed
|
|
36024
36025
|
};
|
|
36025
36026
|
break;
|
|
36027
|
+
case "summaries":
|
|
36028
|
+
next = {
|
|
36029
|
+
...next,
|
|
36030
|
+
summaries: next.summaries + c.rows,
|
|
36031
|
+
bytesReclaimed
|
|
36032
|
+
};
|
|
36033
|
+
break;
|
|
36026
36034
|
}
|
|
36027
36035
|
}
|
|
36028
36036
|
return next;
|
|
@@ -36036,6 +36044,7 @@ function mergeCascadeTallies(a, b) {
|
|
|
36036
36044
|
faces: a.faces + b.faces,
|
|
36037
36045
|
plates: a.plates + b.plates,
|
|
36038
36046
|
embeddings: a.embeddings + b.embeddings,
|
|
36047
|
+
summaries: a.summaries + b.summaries,
|
|
36039
36048
|
bytesReclaimed: a.bytesReclaimed + b.bytesReclaimed
|
|
36040
36049
|
};
|
|
36041
36050
|
}
|
|
@@ -36098,6 +36107,7 @@ function absentCascadeLegs(stores) {
|
|
|
36098
36107
|
if (!stores.faceStore) absent.push("faces");
|
|
36099
36108
|
if (!stores.plateStore) absent.push("plates");
|
|
36100
36109
|
if (!stores.objectEmbeddingStore) absent.push("embeddings");
|
|
36110
|
+
if (!stores.summaryStore) absent.push("summaries");
|
|
36101
36111
|
return absent;
|
|
36102
36112
|
}
|
|
36103
36113
|
function buildAnalyticsCascadeRegistry(stores) {
|
|
@@ -36105,6 +36115,7 @@ function buildAnalyticsCascadeRegistry(stores) {
|
|
|
36105
36115
|
if (stores.faceStore) registry.push(stores.faceStore);
|
|
36106
36116
|
if (stores.plateStore) registry.push(stores.plateStore);
|
|
36107
36117
|
if (stores.objectEmbeddingStore) registry.push(stores.objectEmbeddingStore);
|
|
36118
|
+
if (stores.summaryStore) registry.push(stores.summaryStore);
|
|
36108
36119
|
return registry;
|
|
36109
36120
|
}
|
|
36110
36121
|
/**
|
|
@@ -56314,6 +56325,344 @@ async function reconcileGallerySamples(deps, options) {
|
|
|
56314
56325
|
stoppedByCeiling
|
|
56315
56326
|
};
|
|
56316
56327
|
}
|
|
56328
|
+
//#endregion
|
|
56329
|
+
//#region src/pipeline-analytics/summary/summary-store.ts
|
|
56330
|
+
var SUMMARIES_COLLECTION = "pipeline-analytics:summaries";
|
|
56331
|
+
var SUMMARY_MEMBERS_COLLECTION = "pipeline-analytics:summary-members";
|
|
56332
|
+
var SUMMARIES_COLUMNS = [
|
|
56333
|
+
{
|
|
56334
|
+
name: "id",
|
|
56335
|
+
type: "TEXT",
|
|
56336
|
+
primaryKey: true,
|
|
56337
|
+
notNull: true
|
|
56338
|
+
},
|
|
56339
|
+
{
|
|
56340
|
+
name: "deviceId",
|
|
56341
|
+
type: "INTEGER",
|
|
56342
|
+
notNull: true
|
|
56343
|
+
},
|
|
56344
|
+
{
|
|
56345
|
+
name: "openedAt",
|
|
56346
|
+
type: "INTEGER",
|
|
56347
|
+
notNull: true
|
|
56348
|
+
},
|
|
56349
|
+
{
|
|
56350
|
+
name: "lastActiveAt",
|
|
56351
|
+
type: "INTEGER",
|
|
56352
|
+
notNull: true
|
|
56353
|
+
},
|
|
56354
|
+
{
|
|
56355
|
+
name: "sealedAt",
|
|
56356
|
+
type: "INTEGER"
|
|
56357
|
+
},
|
|
56358
|
+
(
|
|
56359
|
+
/** `deviceId` while open, NULL once sealed — the unique "one open per camera". */
|
|
56360
|
+
{
|
|
56361
|
+
name: "openDeviceId",
|
|
56362
|
+
type: "INTEGER"
|
|
56363
|
+
}),
|
|
56364
|
+
{
|
|
56365
|
+
name: "memberCount",
|
|
56366
|
+
type: "INTEGER",
|
|
56367
|
+
notNull: true
|
|
56368
|
+
}
|
|
56369
|
+
];
|
|
56370
|
+
var SUMMARIES_INDEXES = [{
|
|
56371
|
+
name: "summaries_open_device",
|
|
56372
|
+
columns: ["openDeviceId"],
|
|
56373
|
+
unique: true
|
|
56374
|
+
}, {
|
|
56375
|
+
name: "summaries_device_opened",
|
|
56376
|
+
columns: ["deviceId", "openedAt"]
|
|
56377
|
+
}];
|
|
56378
|
+
var SUMMARY_MEMBERS_COLUMNS = [
|
|
56379
|
+
{
|
|
56380
|
+
name: "id",
|
|
56381
|
+
type: "TEXT",
|
|
56382
|
+
primaryKey: true,
|
|
56383
|
+
notNull: true
|
|
56384
|
+
},
|
|
56385
|
+
{
|
|
56386
|
+
name: "summaryId",
|
|
56387
|
+
type: "TEXT",
|
|
56388
|
+
notNull: true
|
|
56389
|
+
},
|
|
56390
|
+
{
|
|
56391
|
+
name: "trackId",
|
|
56392
|
+
type: "TEXT",
|
|
56393
|
+
notNull: true
|
|
56394
|
+
},
|
|
56395
|
+
{
|
|
56396
|
+
name: "deviceId",
|
|
56397
|
+
type: "INTEGER",
|
|
56398
|
+
notNull: true
|
|
56399
|
+
},
|
|
56400
|
+
(
|
|
56401
|
+
/** When the track joined — named `timestamp` so the orphan audit's declared-owner scope reads it. */
|
|
56402
|
+
{
|
|
56403
|
+
name: "timestamp",
|
|
56404
|
+
type: "INTEGER",
|
|
56405
|
+
notNull: true
|
|
56406
|
+
})
|
|
56407
|
+
];
|
|
56408
|
+
var SUMMARY_MEMBERS_INDEXES = [{
|
|
56409
|
+
name: "summary_members_summary",
|
|
56410
|
+
columns: ["summaryId"]
|
|
56411
|
+
}, {
|
|
56412
|
+
name: "summary_members_track",
|
|
56413
|
+
columns: ["trackId"]
|
|
56414
|
+
}];
|
|
56415
|
+
function summaryId(deviceId, openedAt) {
|
|
56416
|
+
return `sum-${deviceId}-${openedAt}`;
|
|
56417
|
+
}
|
|
56418
|
+
function memberRowId(summaryIdValue, trackId) {
|
|
56419
|
+
return `${summaryIdValue}:${trackId}`;
|
|
56420
|
+
}
|
|
56421
|
+
function num$1(value, fallback = 0) {
|
|
56422
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
56423
|
+
}
|
|
56424
|
+
function toRow$1(id, data) {
|
|
56425
|
+
return {
|
|
56426
|
+
id,
|
|
56427
|
+
deviceId: num$1(data["deviceId"]),
|
|
56428
|
+
openedAt: num$1(data["openedAt"]),
|
|
56429
|
+
lastActiveAt: num$1(data["lastActiveAt"]),
|
|
56430
|
+
sealedAt: typeof data["sealedAt"] === "number" ? data["sealedAt"] : null,
|
|
56431
|
+
memberCount: num$1(data["memberCount"])
|
|
56432
|
+
};
|
|
56433
|
+
}
|
|
56434
|
+
function isUniqueViolation(err) {
|
|
56435
|
+
return err instanceof Error && /UNIQUE constraint failed/i.test(err.message);
|
|
56436
|
+
}
|
|
56437
|
+
var SummaryStore = class {
|
|
56438
|
+
store;
|
|
56439
|
+
logger;
|
|
56440
|
+
constructor(deps) {
|
|
56441
|
+
this.store = deps.store;
|
|
56442
|
+
this.logger = deps.logger;
|
|
56443
|
+
}
|
|
56444
|
+
static async declare(store) {
|
|
56445
|
+
await Promise.all([store.declareCollection.mutate({
|
|
56446
|
+
collection: SUMMARIES_COLLECTION,
|
|
56447
|
+
columns: [...SUMMARIES_COLUMNS],
|
|
56448
|
+
indexes: [...SUMMARIES_INDEXES]
|
|
56449
|
+
}), store.declareCollection.mutate({
|
|
56450
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
56451
|
+
columns: [...SUMMARY_MEMBERS_COLUMNS],
|
|
56452
|
+
indexes: [...SUMMARY_MEMBERS_INDEXES]
|
|
56453
|
+
})]);
|
|
56454
|
+
}
|
|
56455
|
+
/** The camera's OPEN summary, from the row — `null` when none. */
|
|
56456
|
+
async findOpen(deviceId) {
|
|
56457
|
+
const first = (await this.store.query.query({
|
|
56458
|
+
collection: SUMMARIES_COLLECTION,
|
|
56459
|
+
filter: {
|
|
56460
|
+
where: { openDeviceId: deviceId },
|
|
56461
|
+
limit: 1
|
|
56462
|
+
}
|
|
56463
|
+
}))[0];
|
|
56464
|
+
return first === void 0 ? null : toRow$1(first.id, first.data);
|
|
56465
|
+
}
|
|
56466
|
+
async listOpen() {
|
|
56467
|
+
return (await this.store.query.query({
|
|
56468
|
+
collection: SUMMARIES_COLLECTION,
|
|
56469
|
+
filter: {
|
|
56470
|
+
where: { sealedAt: null },
|
|
56471
|
+
limit: 1e4
|
|
56472
|
+
}
|
|
56473
|
+
})).filter((r) => r.data["openDeviceId"] !== null).map((r) => toRow$1(r.id, r.data));
|
|
56474
|
+
}
|
|
56475
|
+
async membersOf(summaryIdValue) {
|
|
56476
|
+
return (await this.store.query.query({
|
|
56477
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
56478
|
+
filter: {
|
|
56479
|
+
where: { summaryId: summaryIdValue },
|
|
56480
|
+
limit: 1e4
|
|
56481
|
+
}
|
|
56482
|
+
})).map((r) => ({
|
|
56483
|
+
summaryId: summaryIdValue,
|
|
56484
|
+
trackId: String(r.data["trackId"] ?? ""),
|
|
56485
|
+
deviceId: num$1(r.data["deviceId"]),
|
|
56486
|
+
joinedAt: num$1(r.data["timestamp"])
|
|
56487
|
+
}));
|
|
56488
|
+
}
|
|
56489
|
+
/**
|
|
56490
|
+
* Put a track into the camera's open summary, opening one when there is
|
|
56491
|
+
* none. Racing opens are settled by the store: the loser's insert hits the
|
|
56492
|
+
* unique `openDeviceId` and is re-read as an absorb.
|
|
56493
|
+
*/
|
|
56494
|
+
async openOrAbsorb(input) {
|
|
56495
|
+
const existing = await this.findOpen(input.deviceId);
|
|
56496
|
+
if (existing !== null) return this.absorb(existing, input);
|
|
56497
|
+
const id = summaryId(input.deviceId, input.at);
|
|
56498
|
+
try {
|
|
56499
|
+
await this.store.set.mutate({
|
|
56500
|
+
collection: SUMMARIES_COLLECTION,
|
|
56501
|
+
key: id,
|
|
56502
|
+
value: {
|
|
56503
|
+
deviceId: input.deviceId,
|
|
56504
|
+
openedAt: input.at,
|
|
56505
|
+
lastActiveAt: input.at,
|
|
56506
|
+
sealedAt: null,
|
|
56507
|
+
openDeviceId: input.deviceId,
|
|
56508
|
+
memberCount: 1
|
|
56509
|
+
}
|
|
56510
|
+
});
|
|
56511
|
+
} catch (err) {
|
|
56512
|
+
if (!isUniqueViolation(err)) throw err;
|
|
56513
|
+
const winner = await this.findOpen(input.deviceId);
|
|
56514
|
+
if (winner === null) throw err;
|
|
56515
|
+
return this.absorb(winner, input);
|
|
56516
|
+
}
|
|
56517
|
+
await this.addMember(id, input);
|
|
56518
|
+
return {
|
|
56519
|
+
id,
|
|
56520
|
+
deviceId: input.deviceId,
|
|
56521
|
+
openedAt: input.at,
|
|
56522
|
+
lastActiveAt: input.at,
|
|
56523
|
+
sealedAt: null,
|
|
56524
|
+
memberCount: 1
|
|
56525
|
+
};
|
|
56526
|
+
}
|
|
56527
|
+
async absorb(summary, input) {
|
|
56528
|
+
const members = await this.membersOf(summary.id);
|
|
56529
|
+
if (members.some((m) => m.trackId === input.trackId)) return summary;
|
|
56530
|
+
await this.addMember(summary.id, input);
|
|
56531
|
+
const memberCount = members.length + 1;
|
|
56532
|
+
const lastActiveAt = Math.max(summary.lastActiveAt, input.at);
|
|
56533
|
+
await this.store.updateWhere.mutate({
|
|
56534
|
+
collection: SUMMARIES_COLLECTION,
|
|
56535
|
+
filter: { where: {
|
|
56536
|
+
openDeviceId: summary.deviceId,
|
|
56537
|
+
openedAt: summary.openedAt
|
|
56538
|
+
} },
|
|
56539
|
+
data: {
|
|
56540
|
+
memberCount,
|
|
56541
|
+
lastActiveAt
|
|
56542
|
+
}
|
|
56543
|
+
});
|
|
56544
|
+
return {
|
|
56545
|
+
...summary,
|
|
56546
|
+
memberCount,
|
|
56547
|
+
lastActiveAt
|
|
56548
|
+
};
|
|
56549
|
+
}
|
|
56550
|
+
async addMember(summaryIdValue, input) {
|
|
56551
|
+
await this.store.insertMany.mutate({
|
|
56552
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
56553
|
+
records: [{
|
|
56554
|
+
id: memberRowId(summaryIdValue, input.trackId),
|
|
56555
|
+
data: {
|
|
56556
|
+
summaryId: summaryIdValue,
|
|
56557
|
+
trackId: input.trackId,
|
|
56558
|
+
deviceId: input.deviceId,
|
|
56559
|
+
timestamp: input.at
|
|
56560
|
+
}
|
|
56561
|
+
}]
|
|
56562
|
+
});
|
|
56563
|
+
}
|
|
56564
|
+
/**
|
|
56565
|
+
* Seal in place. `true` when THIS call sealed it; `false` when the row was
|
|
56566
|
+
* already sealed (or gone) — the second sealer learns, it does not write.
|
|
56567
|
+
*/
|
|
56568
|
+
async seal(summary, sealedAt) {
|
|
56569
|
+
const { updated } = await this.store.updateWhere.mutate({
|
|
56570
|
+
collection: SUMMARIES_COLLECTION,
|
|
56571
|
+
filter: { where: {
|
|
56572
|
+
openDeviceId: summary.deviceId,
|
|
56573
|
+
openedAt: summary.openedAt
|
|
56574
|
+
} },
|
|
56575
|
+
data: {
|
|
56576
|
+
sealedAt,
|
|
56577
|
+
openDeviceId: null
|
|
56578
|
+
}
|
|
56579
|
+
});
|
|
56580
|
+
if (updated > 1) this.logger.error("summary seal touched more than one row — the open key is not unique", {
|
|
56581
|
+
tags: { deviceId: summary.deviceId },
|
|
56582
|
+
meta: {
|
|
56583
|
+
summaryId: summary.id,
|
|
56584
|
+
updated
|
|
56585
|
+
}
|
|
56586
|
+
});
|
|
56587
|
+
return updated >= 1;
|
|
56588
|
+
}
|
|
56589
|
+
/**
|
|
56590
|
+
* Track cascade leaf: a member row whose track is gone is gone too, and
|
|
56591
|
+
* the summary's count follows. The envelope itself outlives its members
|
|
56592
|
+
* — it is an age-keyed root (`RETENTION_ROOTS`, `summary`).
|
|
56593
|
+
*/
|
|
56594
|
+
async deleteByTracks(trackIds) {
|
|
56595
|
+
if (trackIds.length === 0) return [{
|
|
56596
|
+
family: "summaries",
|
|
56597
|
+
rows: 0,
|
|
56598
|
+
bytes: 0
|
|
56599
|
+
}];
|
|
56600
|
+
const members = await this.store.query.query({
|
|
56601
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
56602
|
+
filter: {
|
|
56603
|
+
whereIn: { trackId: [...trackIds] },
|
|
56604
|
+
limit: 1e4
|
|
56605
|
+
}
|
|
56606
|
+
});
|
|
56607
|
+
if (members.length === 0) return [{
|
|
56608
|
+
family: "summaries",
|
|
56609
|
+
rows: 0,
|
|
56610
|
+
bytes: 0
|
|
56611
|
+
}];
|
|
56612
|
+
const { deleted } = await this.store.deleteWhere.mutate({
|
|
56613
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
56614
|
+
filter: { whereIn: { trackId: [...trackIds] } }
|
|
56615
|
+
});
|
|
56616
|
+
const touched = new Set(members.map((m) => String(m.data["summaryId"] ?? "")));
|
|
56617
|
+
for (const summaryIdValue of touched) {
|
|
56618
|
+
const remaining = await this.membersOf(summaryIdValue);
|
|
56619
|
+
const raw = await this.store.get.query({
|
|
56620
|
+
collection: SUMMARIES_COLLECTION,
|
|
56621
|
+
key: summaryIdValue
|
|
56622
|
+
});
|
|
56623
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
56624
|
+
const row = raw;
|
|
56625
|
+
await this.store.updateWhere.mutate({
|
|
56626
|
+
collection: SUMMARIES_COLLECTION,
|
|
56627
|
+
filter: { where: {
|
|
56628
|
+
deviceId: num$1(row["deviceId"]),
|
|
56629
|
+
openedAt: num$1(row["openedAt"])
|
|
56630
|
+
} },
|
|
56631
|
+
data: { memberCount: remaining.length }
|
|
56632
|
+
});
|
|
56633
|
+
}
|
|
56634
|
+
return [{
|
|
56635
|
+
family: "summaries",
|
|
56636
|
+
rows: deleted,
|
|
56637
|
+
bytes: 0
|
|
56638
|
+
}];
|
|
56639
|
+
}
|
|
56640
|
+
/** Age sweep on the device window: everything opened before `cutoffMs`, members included. */
|
|
56641
|
+
async evictBefore(deviceId, cutoffMs) {
|
|
56642
|
+
const rows = await this.store.query.query({
|
|
56643
|
+
collection: SUMMARIES_COLLECTION,
|
|
56644
|
+
filter: {
|
|
56645
|
+
where: { deviceId },
|
|
56646
|
+
whereBetween: { openedAt: [0, cutoffMs - 1] },
|
|
56647
|
+
limit: 1e4
|
|
56648
|
+
}
|
|
56649
|
+
});
|
|
56650
|
+
if (rows.length === 0) return 0;
|
|
56651
|
+
const ids = rows.map((r) => r.id);
|
|
56652
|
+
await this.store.deleteWhere.mutate({
|
|
56653
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
56654
|
+
filter: { whereIn: { summaryId: ids } }
|
|
56655
|
+
});
|
|
56656
|
+
const { deleted } = await this.store.deleteWhere.mutate({
|
|
56657
|
+
collection: SUMMARIES_COLLECTION,
|
|
56658
|
+
filter: {
|
|
56659
|
+
where: { deviceId },
|
|
56660
|
+
whereBetween: { openedAt: [0, cutoffMs - 1] }
|
|
56661
|
+
}
|
|
56662
|
+
});
|
|
56663
|
+
return deleted;
|
|
56664
|
+
}
|
|
56665
|
+
};
|
|
56317
56666
|
/**
|
|
56318
56667
|
* Where the completion marker lives.
|
|
56319
56668
|
*
|
|
@@ -58609,6 +58958,10 @@ var ANALYTICS_COLLECTIONS = [
|
|
|
58609
58958
|
collection: PLATES_COLLECTION,
|
|
58610
58959
|
classification: derived(PLATE_COLUMNS)
|
|
58611
58960
|
},
|
|
58961
|
+
{
|
|
58962
|
+
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
58963
|
+
classification: derived(SUMMARY_MEMBERS_COLUMNS)
|
|
58964
|
+
},
|
|
58612
58965
|
{
|
|
58613
58966
|
collection: MEDIA_COLLECTION,
|
|
58614
58967
|
classification: {
|
|
@@ -58652,6 +59005,13 @@ var ANALYTICS_COLLECTIONS = [
|
|
|
58652
59005
|
rootId: "ops-log"
|
|
58653
59006
|
}
|
|
58654
59007
|
},
|
|
59008
|
+
{
|
|
59009
|
+
collection: SUMMARIES_COLLECTION,
|
|
59010
|
+
classification: {
|
|
59011
|
+
kind: "retention-root",
|
|
59012
|
+
rootId: "summary"
|
|
59013
|
+
}
|
|
59014
|
+
},
|
|
58655
59015
|
{
|
|
58656
59016
|
collection: IDENTITIES_COLLECTION,
|
|
58657
59017
|
classification: {
|
|
@@ -59212,12 +59572,14 @@ async function sweepTracklessRoots(deps) {
|
|
|
59212
59572
|
cutoffMs
|
|
59213
59573
|
});
|
|
59214
59574
|
const sensor = deps.evictSensorEvents ? await deps.evictSensorEvents(deviceId, cutoffMs) : 0;
|
|
59575
|
+
const summaries = deps.evictSummaries ? await deps.evictSummaries(deviceId, cutoffMs) : 0;
|
|
59215
59576
|
const evicted = {
|
|
59216
59577
|
motion: events.motion,
|
|
59217
59578
|
audio: events.audio,
|
|
59218
|
-
sensor
|
|
59579
|
+
sensor,
|
|
59580
|
+
summaries
|
|
59219
59581
|
};
|
|
59220
|
-
if (evicted.motion.length + evicted.audio.length + evicted.sensor > 0) await deps.onEvicted(deviceId, evicted, cutoffs);
|
|
59582
|
+
if (evicted.motion.length + evicted.audio.length + evicted.sensor + evicted.summaries > 0) await deps.onEvicted(deviceId, evicted, cutoffs);
|
|
59221
59583
|
} catch (err) {
|
|
59222
59584
|
deps.onError(deviceId, err);
|
|
59223
59585
|
}
|
|
@@ -64912,6 +65274,97 @@ function rowFromMetadata(id, metadata) {
|
|
|
64912
65274
|
};
|
|
64913
65275
|
}
|
|
64914
65276
|
//#endregion
|
|
65277
|
+
//#region src/pipeline-analytics/summary/summary-sealer.ts
|
|
65278
|
+
function errMsg(err) {
|
|
65279
|
+
return err instanceof Error ? err.message : String(err);
|
|
65280
|
+
}
|
|
65281
|
+
var SummarySealer = class {
|
|
65282
|
+
deps;
|
|
65283
|
+
sweepInFlight = false;
|
|
65284
|
+
constructor(deps) {
|
|
65285
|
+
this.deps = deps;
|
|
65286
|
+
}
|
|
65287
|
+
async onTrackOpened(input) {
|
|
65288
|
+
try {
|
|
65289
|
+
await this.deps.summaries.openOrAbsorb({
|
|
65290
|
+
deviceId: input.deviceId,
|
|
65291
|
+
trackId: input.trackId,
|
|
65292
|
+
at: this.deps.now()
|
|
65293
|
+
});
|
|
65294
|
+
} catch (err) {
|
|
65295
|
+
this.deps.logger.warn("summary: could not open or absorb for a track", {
|
|
65296
|
+
tags: { deviceId: input.deviceId },
|
|
65297
|
+
meta: {
|
|
65298
|
+
trackId: input.trackId,
|
|
65299
|
+
error: errMsg(err)
|
|
65300
|
+
}
|
|
65301
|
+
});
|
|
65302
|
+
}
|
|
65303
|
+
}
|
|
65304
|
+
/** One sweep at a time (THE latch). A tick that finds one in flight returns. */
|
|
65305
|
+
async sweep() {
|
|
65306
|
+
if (this.sweepInFlight) return;
|
|
65307
|
+
this.sweepInFlight = true;
|
|
65308
|
+
try {
|
|
65309
|
+
const open = await this.deps.summaries.listOpen();
|
|
65310
|
+
for (const summary of open) await this.sealIfDone(summary);
|
|
65311
|
+
} catch (err) {
|
|
65312
|
+
this.deps.logger.warn("summary sweep: could not list open summaries", { meta: { error: errMsg(err) } });
|
|
65313
|
+
} finally {
|
|
65314
|
+
this.sweepInFlight = false;
|
|
65315
|
+
}
|
|
65316
|
+
}
|
|
65317
|
+
async sealIfDone(summary) {
|
|
65318
|
+
const { id: summaryId, deviceId } = summary;
|
|
65319
|
+
let openTracks;
|
|
65320
|
+
try {
|
|
65321
|
+
openTracks = await this.deps.openTrackIds(deviceId);
|
|
65322
|
+
} catch (err) {
|
|
65323
|
+
this.deps.logger.warn("summary sweep: could not read open tracks — seal aborted this sweep", {
|
|
65324
|
+
tags: { deviceId },
|
|
65325
|
+
meta: {
|
|
65326
|
+
summaryId,
|
|
65327
|
+
error: errMsg(err)
|
|
65328
|
+
}
|
|
65329
|
+
});
|
|
65330
|
+
return;
|
|
65331
|
+
}
|
|
65332
|
+
if (openTracks.length > 0) return;
|
|
65333
|
+
let quiet;
|
|
65334
|
+
try {
|
|
65335
|
+
quiet = await this.deps.isCameraQuiet(deviceId);
|
|
65336
|
+
} catch (err) {
|
|
65337
|
+
this.deps.logger.warn("summary sweep: could not read the stationary registry — seal aborted this sweep", {
|
|
65338
|
+
tags: { deviceId },
|
|
65339
|
+
meta: {
|
|
65340
|
+
summaryId,
|
|
65341
|
+
error: errMsg(err)
|
|
65342
|
+
}
|
|
65343
|
+
});
|
|
65344
|
+
return;
|
|
65345
|
+
}
|
|
65346
|
+
if (!quiet) return;
|
|
65347
|
+
const sealedAt = this.deps.now();
|
|
65348
|
+
try {
|
|
65349
|
+
if (await this.deps.summaries.seal(summary, sealedAt)) this.deps.logger.info("summary sealed", {
|
|
65350
|
+
tags: { deviceId },
|
|
65351
|
+
meta: {
|
|
65352
|
+
summaryId,
|
|
65353
|
+
sealedAt
|
|
65354
|
+
}
|
|
65355
|
+
});
|
|
65356
|
+
} catch (err) {
|
|
65357
|
+
this.deps.logger.warn("summary sweep: seal failed — the row stays open for the next sweep", {
|
|
65358
|
+
tags: { deviceId },
|
|
65359
|
+
meta: {
|
|
65360
|
+
summaryId,
|
|
65361
|
+
error: errMsg(err)
|
|
65362
|
+
}
|
|
65363
|
+
});
|
|
65364
|
+
}
|
|
65365
|
+
}
|
|
65366
|
+
};
|
|
65367
|
+
//#endregion
|
|
64915
65368
|
//#region src/pipeline-analytics/store/settings-raw-mirror.ts
|
|
64916
65369
|
/** How long a mirrored entry is served before a refresh is started behind the
|
|
64917
65370
|
* next reader. See the module doc for why this matches the per-kind caches'
|
|
@@ -66224,6 +66677,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
66224
66677
|
storageMigrationLeaseId = null;
|
|
66225
66678
|
/** Per-camera history of LINKED-device sensor state changes (Part B). */
|
|
66226
66679
|
sensorEventStore = null;
|
|
66680
|
+
/** The per-camera session envelope (D359). Bound with the other stores at boot. */
|
|
66681
|
+
summaryStore = null;
|
|
66682
|
+
/** The ONLY writer of a summary's lifecycle (D359). */
|
|
66683
|
+
summarySealer = null;
|
|
66227
66684
|
/** Ingest-side reverse index (sensor device → linked camera ids), TTL-cached
|
|
66228
66685
|
* so the DeviceStateChanged handler stays cheap. */
|
|
66229
66686
|
linkedCamerasCache = null;
|
|
@@ -67462,6 +67919,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
67462
67919
|
MediaStore.declare(api.settingsStore),
|
|
67463
67920
|
EventStore.declare(api.settingsStore),
|
|
67464
67921
|
SensorEventStore.declare(api.settingsStore),
|
|
67922
|
+
SummaryStore.declare(api.settingsStore),
|
|
67465
67923
|
IdentityStore.declare(api.settingsStore),
|
|
67466
67924
|
FaceStore.declare(api.settingsStore),
|
|
67467
67925
|
PlateStore.declare(api.settingsStore),
|
|
@@ -67723,6 +68181,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
67723
68181
|
store: api.settingsStore,
|
|
67724
68182
|
logger: logger.child("SensorEventStore")
|
|
67725
68183
|
});
|
|
68184
|
+
this.summaryStore = new SummaryStore({
|
|
68185
|
+
store: api.settingsStore,
|
|
68186
|
+
logger: this.ctx.logger.child("summary")
|
|
68187
|
+
});
|
|
68188
|
+
this.summarySealer = new SummarySealer({
|
|
68189
|
+
summaries: this.summaryStore,
|
|
68190
|
+
openTrackIds: async (deviceId) => this.residents.trackIdsForDevice(deviceId),
|
|
68191
|
+
isCameraQuiet: async (deviceId) => (this.stationaryRegistry?.count(deviceId) ?? 0) === 0,
|
|
68192
|
+
now: () => Date.now(),
|
|
68193
|
+
logger: this.ctx.logger.child("summary-sealer")
|
|
68194
|
+
});
|
|
67726
68195
|
this.packageDropDetector = new PackageDropDetector({
|
|
67727
68196
|
events: eventStore,
|
|
67728
68197
|
emit: {
|
|
@@ -69935,6 +70404,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
69935
70404
|
className: t.className
|
|
69936
70405
|
}
|
|
69937
70406
|
});
|
|
70407
|
+
this.summarySealer?.onTrackOpened({
|
|
70408
|
+
deviceId,
|
|
70409
|
+
trackId: id
|
|
70410
|
+
});
|
|
69938
70411
|
const startPayload = buildTrackLifecyclePayload({
|
|
69939
70412
|
deviceId,
|
|
69940
70413
|
trackId: id,
|
|
@@ -71959,6 +72432,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71959
72432
|
async sweepExpiredTracks() {
|
|
71960
72433
|
if (this.shuttingDown || !this.trackStore) return;
|
|
71961
72434
|
await this.trackCloser.sweep();
|
|
72435
|
+
await this.summarySealer?.sweep();
|
|
71962
72436
|
}
|
|
71963
72437
|
/** Unified retention (Phase 1): earliest retained footage per device — the
|
|
71964
72438
|
* follow-recordings cutoff source. Cached 10 min: availability answers in
|
|
@@ -72055,6 +72529,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
72055
72529
|
await this.runTrackRetentionSweep(deviceIds, now);
|
|
72056
72530
|
const eventStore = this.eventStore;
|
|
72057
72531
|
const sensorEventStore = this.sensorEventStore;
|
|
72532
|
+
const summaryStore = this.summaryStore;
|
|
72058
72533
|
await sweepTracklessRoots({
|
|
72059
72534
|
deviceIds,
|
|
72060
72535
|
nowMs: now,
|
|
@@ -72064,15 +72539,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
72064
72539
|
cutoffMs
|
|
72065
72540
|
}),
|
|
72066
72541
|
evictSensorEvents: sensorEventStore ? (deviceId, cutoffMs) => sensorEventStore.evictBefore(deviceId, cutoffMs) : null,
|
|
72542
|
+
evictSummaries: summaryStore ? (deviceId, cutoffMs) => summaryStore.evictBefore(deviceId, cutoffMs) : null,
|
|
72067
72543
|
onEvicted: async (deviceId, evicted, cutoffs) => {
|
|
72068
|
-
const total = evicted.motion.length + evicted.audio.length + evicted.sensor;
|
|
72544
|
+
const total = evicted.motion.length + evicted.audio.length + evicted.sensor + evicted.summaries;
|
|
72069
72545
|
await this.eventsOpsLog?.append({
|
|
72070
72546
|
op: "prune",
|
|
72071
72547
|
reason: "retention",
|
|
72072
72548
|
deviceId,
|
|
72073
72549
|
itemsAffected: total,
|
|
72074
72550
|
bytesReclaimed: 0,
|
|
72075
|
-
detail: `trackless age sweep (${cutoffs.effectiveMode}): ${evicted.motion.length} motion, ${evicted.audio.length} audio, ${evicted.sensor} sensor`
|
|
72551
|
+
detail: `trackless age sweep (${cutoffs.effectiveMode}): ${evicted.motion.length} motion, ${evicted.audio.length} audio, ${evicted.sensor} sensor, ${evicted.summaries} summaries`
|
|
72076
72552
|
});
|
|
72077
72553
|
this.ctx.logger.info("analytics trackless event eviction (age sweep)", {
|
|
72078
72554
|
tags: { deviceId },
|
|
@@ -72080,6 +72556,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
72080
72556
|
motion: evicted.motion.length,
|
|
72081
72557
|
audio: evicted.audio.length,
|
|
72082
72558
|
sensor: evicted.sensor,
|
|
72559
|
+
summaries: evicted.summaries,
|
|
72083
72560
|
mode: cutoffs.effectiveMode,
|
|
72084
72561
|
cutoffMs: cutoffs.cutoffMs
|
|
72085
72562
|
}
|
|
@@ -74559,7 +75036,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
74559
75036
|
mediaStore,
|
|
74560
75037
|
faceStore: this.faceStore,
|
|
74561
75038
|
plateStore: this.plateStore,
|
|
74562
|
-
objectEmbeddingStore: this.objectEmbeddingStore
|
|
75039
|
+
objectEmbeddingStore: this.objectEmbeddingStore,
|
|
75040
|
+
summaryStore: this.summaryStore
|
|
74563
75041
|
};
|
|
74564
75042
|
const absent = absentCascadeLegs(stores);
|
|
74565
75043
|
if (absent.length > 0) this.ctx.logger.warn("track cascade is missing a leg — its rows will be left behind", { meta: {
|