@camstack/addon-post-analysis 1.2.182 → 1.2.183
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/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BirG5etn.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-ffBUTS31.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-DZG5yDTK.mjs → hostInit-x1-ijfAu.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +378 -38
- package/dist/pipeline-analytics/index.mjs +378 -38
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -27813,6 +27813,7 @@ var StoragePressureTracker = class {
|
|
|
27813
27813
|
var OWNER_KINDS = [
|
|
27814
27814
|
"event",
|
|
27815
27815
|
"track",
|
|
27816
|
+
"summary",
|
|
27816
27817
|
"face",
|
|
27817
27818
|
"identity",
|
|
27818
27819
|
"plate",
|
|
@@ -28396,7 +28397,8 @@ var MediaStore = class {
|
|
|
28396
28397
|
relativePath: path
|
|
28397
28398
|
})).toString("base64"),
|
|
28398
28399
|
sizeBytes,
|
|
28399
|
-
timestamp
|
|
28400
|
+
timestamp,
|
|
28401
|
+
deviceId: Number(data["deviceId"])
|
|
28400
28402
|
};
|
|
28401
28403
|
} catch {
|
|
28402
28404
|
return null;
|
|
@@ -30670,7 +30672,7 @@ async function mapWithConcurrency(items, limit, mapper) {
|
|
|
30670
30672
|
* all-signals-maxed track scores exactly 1.0 (the clamp is then a no-op) and an
|
|
30671
30673
|
* all-zero track scores 0.
|
|
30672
30674
|
*/
|
|
30673
|
-
var WEIGHT_CONFIDENCE = .3;
|
|
30675
|
+
var WEIGHT_CONFIDENCE$1 = .3;
|
|
30674
30676
|
var WEIGHT_CLASS = .2;
|
|
30675
30677
|
var WEIGHT_DWELL = .2;
|
|
30676
30678
|
var WEIGHT_PROXIMITY = .12;
|
|
@@ -30708,7 +30710,7 @@ var ANIMAL_CLASSES = new Set([
|
|
|
30708
30710
|
"cat"
|
|
30709
30711
|
]);
|
|
30710
30712
|
/** Clamp to [0,1]. */
|
|
30711
|
-
function clamp01$
|
|
30713
|
+
function clamp01$2(x) {
|
|
30712
30714
|
if (Number.isNaN(x)) return 0;
|
|
30713
30715
|
if (x < 0) return 0;
|
|
30714
30716
|
if (x > 1) return 1;
|
|
@@ -30742,7 +30744,7 @@ function computeImportance(input) {
|
|
|
30742
30744
|
const terms = [
|
|
30743
30745
|
{
|
|
30744
30746
|
reason: "confidence",
|
|
30745
|
-
value: WEIGHT_CONFIDENCE * clamp01$
|
|
30747
|
+
value: WEIGHT_CONFIDENCE$1 * clamp01$2(input.peakConfidence)
|
|
30746
30748
|
},
|
|
30747
30749
|
{
|
|
30748
30750
|
reason: "class",
|
|
@@ -30750,15 +30752,15 @@ function computeImportance(input) {
|
|
|
30750
30752
|
},
|
|
30751
30753
|
{
|
|
30752
30754
|
reason: "dwell",
|
|
30753
|
-
value: WEIGHT_DWELL * clamp01$
|
|
30755
|
+
value: WEIGHT_DWELL * clamp01$2(input.durationMs / DWELL_FULL_MS)
|
|
30754
30756
|
},
|
|
30755
30757
|
{
|
|
30756
30758
|
reason: "proximity",
|
|
30757
|
-
value: WEIGHT_PROXIMITY * clamp01$
|
|
30759
|
+
value: WEIGHT_PROXIMITY * clamp01$2(input.peakBboxAreaFrac / SIZE_FULL)
|
|
30758
30760
|
},
|
|
30759
30761
|
{
|
|
30760
30762
|
reason: "travel",
|
|
30761
|
-
value: WEIGHT_TRAVEL * clamp01$
|
|
30763
|
+
value: WEIGHT_TRAVEL * clamp01$2(input.totalDistance / 2)
|
|
30762
30764
|
},
|
|
30763
30765
|
{
|
|
30764
30766
|
reason: "zone",
|
|
@@ -30777,11 +30779,11 @@ function computeImportance(input) {
|
|
|
30777
30779
|
}
|
|
30778
30780
|
const identified = input.identityLabel !== void 0 && input.identityLabel.length > 0;
|
|
30779
30781
|
if (input.netDisplacementFrac !== void 0 && input.pathSpanFrac !== void 0 && input.netDisplacementFrac < .02 && input.pathSpanFrac < .04 && !identified) return {
|
|
30780
|
-
importance: clamp01$
|
|
30782
|
+
importance: clamp01$2(sum * STATIC_IMPORTANCE_MULTIPLIER),
|
|
30781
30783
|
reason: best.reason
|
|
30782
30784
|
};
|
|
30783
30785
|
return {
|
|
30784
|
-
importance: clamp01$
|
|
30786
|
+
importance: clamp01$2(sum),
|
|
30785
30787
|
reason: best.reason
|
|
30786
30788
|
};
|
|
30787
30789
|
}
|
|
@@ -33479,7 +33481,7 @@ function isEdgeAwareNewBest(current, candidate, hysteresis) {
|
|
|
33479
33481
|
* it, so it can be loose enough to see the near misses.
|
|
33480
33482
|
*/
|
|
33481
33483
|
var CO_MOVING_MIN_CONTAINMENT = .6;
|
|
33482
|
-
function intersectionArea$
|
|
33484
|
+
function intersectionArea$4(a, b) {
|
|
33483
33485
|
return Math.max(0, Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x)) * Math.max(0, Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y));
|
|
33484
33486
|
}
|
|
33485
33487
|
/**
|
|
@@ -33499,7 +33501,7 @@ function findCoMovingPairs(born, live, minContainment = CO_MOVING_MIN_CONTAINMEN
|
|
|
33499
33501
|
if (o.className === b.className) continue;
|
|
33500
33502
|
const areaO = o.bbox.w * o.bbox.h;
|
|
33501
33503
|
if (areaO <= 0) continue;
|
|
33502
|
-
const inter = intersectionArea$
|
|
33504
|
+
const inter = intersectionArea$4(b.bbox, o.bbox);
|
|
33503
33505
|
if (inter <= 0) continue;
|
|
33504
33506
|
const union = areaB + areaO - inter;
|
|
33505
33507
|
const containment = inter / Math.min(areaB, areaO);
|
|
@@ -33931,7 +33933,7 @@ function pairRiders(detections) {
|
|
|
33931
33933
|
* diagonal = 2.085) with the same ~10% headroom the old 1.35 gave it.
|
|
33932
33934
|
*/
|
|
33933
33935
|
var GROWTH_CENTROID_FACTOR = 2.3;
|
|
33934
|
-
function intersectionArea$
|
|
33936
|
+
function intersectionArea$3(a, b) {
|
|
33935
33937
|
const x1 = Math.max(a.x, b.x);
|
|
33936
33938
|
const y1 = Math.max(a.y, b.y);
|
|
33937
33939
|
const x2 = Math.min(a.x + a.w, b.x + b.w);
|
|
@@ -33969,7 +33971,7 @@ function growthAdjacentMatch(track, det, ctx) {
|
|
|
33969
33971
|
const larger = Math.max(trackArea, detArea);
|
|
33970
33972
|
const smaller = Math.min(trackArea, detArea);
|
|
33971
33973
|
if (larger / smaller < 1.5) return false;
|
|
33972
|
-
if (intersectionArea$
|
|
33974
|
+
if (intersectionArea$3(track, det) / smaller >= .5) return false;
|
|
33973
33975
|
const tc = bboxCentroid(track);
|
|
33974
33976
|
const dc = bboxCentroid(det);
|
|
33975
33977
|
return Math.hypot(tc.x - dc.x, tc.y - dc.y) <= GROWTH_CENTROID_FACTOR * Math.hypot(track.w, track.h);
|
|
@@ -46730,6 +46732,15 @@ var CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
|
46730
46732
|
var THUMB_DEFAULT_SIZE = 160;
|
|
46731
46733
|
var THUMB_MIN_SIZE = 64;
|
|
46732
46734
|
var THUMB_MAX_SIZE = 320;
|
|
46735
|
+
/** Set by the HUB for a `csv_*` share token admitted on this plane; never by a client. */
|
|
46736
|
+
var SHARE_DEVICES_HEADER = "x-camstack-share-devices";
|
|
46737
|
+
/** The device ids a share scope allows, or `null` when the request carries no scope. */
|
|
46738
|
+
function shareScopeDevices(headers) {
|
|
46739
|
+
const raw = headers[SHARE_DEVICES_HEADER];
|
|
46740
|
+
const value = Array.isArray(raw) ? raw.join(",") : raw;
|
|
46741
|
+
if (value === void 0) return null;
|
|
46742
|
+
return new Set(value.split(",").map((part) => Number.parseInt(part.trim(), 10)).filter((id) => Number.isInteger(id) && id >= 0));
|
|
46743
|
+
}
|
|
46733
46744
|
/**
|
|
46734
46745
|
* Parse the `?kind=…` query into a preferred stored media kind. Returns null
|
|
46735
46746
|
* when unset. The value is a free-form kind token (e.g. `crop`); the resolver
|
|
@@ -46802,6 +46813,14 @@ function createEventMediaHandler(deps) {
|
|
|
46802
46813
|
res.writeHead(404, { "server-timing": `resolve;dur=${(performance.now() - resolveStartedAt).toFixed(1)}` }).end();
|
|
46803
46814
|
return;
|
|
46804
46815
|
}
|
|
46816
|
+
const scope = shareScopeDevices(req.headers);
|
|
46817
|
+
if (scope !== null) {
|
|
46818
|
+
const owner = media.deviceId ?? null;
|
|
46819
|
+
if (owner === null || !scope.has(owner)) {
|
|
46820
|
+
res.writeHead(403).end();
|
|
46821
|
+
return;
|
|
46822
|
+
}
|
|
46823
|
+
}
|
|
46805
46824
|
const etag = "\"" + media.key + "\"";
|
|
46806
46825
|
const serverTiming = `resolve;dur=${(performance.now() - resolveStartedAt).toFixed(1)}`;
|
|
46807
46826
|
if (req.headers["if-none-match"] === etag) {
|
|
@@ -48272,7 +48291,7 @@ var RetrainAnnotationStore = class {
|
|
|
48272
48291
|
};
|
|
48273
48292
|
//#endregion
|
|
48274
48293
|
//#region src/pipeline-analytics/retrain/retrain-geometry.ts
|
|
48275
|
-
function clamp01(v) {
|
|
48294
|
+
function clamp01$1(v) {
|
|
48276
48295
|
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
48277
48296
|
}
|
|
48278
48297
|
/** Is this a usable rectangle at all? A zero-area box is not a subject. */
|
|
@@ -48348,10 +48367,10 @@ function deriveZoneCropRect(bbox, zone) {
|
|
|
48348
48367
|
function deriveSubjectCropRect(bbox, padding) {
|
|
48349
48368
|
if (!isUsableRect(bbox)) return null;
|
|
48350
48369
|
const pad = Number.isFinite(padding) && padding > 0 ? padding : 0;
|
|
48351
|
-
const cropX = clamp01(bbox.x - bbox.w * pad);
|
|
48352
|
-
const cropY = clamp01(bbox.y - bbox.h * pad);
|
|
48353
|
-
const cropRight = clamp01(bbox.x + bbox.w * (1 + pad));
|
|
48354
|
-
const cropBottom = clamp01(bbox.y + bbox.h * (1 + pad));
|
|
48370
|
+
const cropX = clamp01$1(bbox.x - bbox.w * pad);
|
|
48371
|
+
const cropY = clamp01$1(bbox.y - bbox.h * pad);
|
|
48372
|
+
const cropRight = clamp01$1(bbox.x + bbox.w * (1 + pad));
|
|
48373
|
+
const cropBottom = clamp01$1(bbox.y + bbox.h * (1 + pad));
|
|
48355
48374
|
const cropW = cropRight - cropX;
|
|
48356
48375
|
const cropH = cropBottom - cropY;
|
|
48357
48376
|
if (!(cropW > 0) || !(cropH > 0)) return null;
|
|
@@ -52602,7 +52621,7 @@ function isSceneSizedBox(bbox, frameWidth, frameHeight) {
|
|
|
52602
52621
|
const frame = frameWidth * frameHeight;
|
|
52603
52622
|
return frame > 0 && bbox.w * bbox.h / frame >= .55;
|
|
52604
52623
|
}
|
|
52605
|
-
function intersectionArea$
|
|
52624
|
+
function intersectionArea$2(a, b) {
|
|
52606
52625
|
const ix1 = Math.max(a.x, b.x);
|
|
52607
52626
|
const iy1 = Math.max(a.y, b.y);
|
|
52608
52627
|
const ix2 = Math.min(a.x + a.w, b.x + b.w);
|
|
@@ -52615,7 +52634,7 @@ function detectionSwallowsEntry(det, parked) {
|
|
|
52615
52634
|
const detArea = det.w * det.h;
|
|
52616
52635
|
if (entryArea <= 0 || detArea < entryArea * 1.35) return false;
|
|
52617
52636
|
if (!centroidInside(det, centroid(parked))) return false;
|
|
52618
|
-
return intersectionArea$
|
|
52637
|
+
return intersectionArea$2(parked, det) / entryArea >= SWALLOW_MIN_COVERAGE;
|
|
52619
52638
|
}
|
|
52620
52639
|
function parkedEntryHoldsDetection(entry, detBbox, config) {
|
|
52621
52640
|
const overlap = iou(entry.bbox, detBbox);
|
|
@@ -53447,7 +53466,7 @@ var VEHICLE_CLASSES = [
|
|
|
53447
53466
|
"vehicle"
|
|
53448
53467
|
];
|
|
53449
53468
|
/** Area of the intersection of two boxes. `0` when they do not overlap. */
|
|
53450
|
-
function intersectionArea(a, b) {
|
|
53469
|
+
function intersectionArea$1(a, b) {
|
|
53451
53470
|
const left = Math.max(a.x, b.x);
|
|
53452
53471
|
const right = Math.min(a.x + a.w, b.x + b.w);
|
|
53453
53472
|
const top = Math.max(a.y, b.y);
|
|
@@ -53464,7 +53483,7 @@ function intersectionArea(a, b) {
|
|
|
53464
53483
|
function containedFraction(person, vehicle) {
|
|
53465
53484
|
const area = person.w * person.h;
|
|
53466
53485
|
if (area <= 0) return 0;
|
|
53467
|
-
return intersectionArea(person, vehicle) / area;
|
|
53486
|
+
return intersectionArea$1(person, vehicle) / area;
|
|
53468
53487
|
}
|
|
53469
53488
|
/**
|
|
53470
53489
|
* Is this person box contained in any vehicle box in the same frame?
|
|
@@ -56363,9 +56382,11 @@ function isUniqueViolation(err) {
|
|
|
56363
56382
|
var SummaryStore = class {
|
|
56364
56383
|
store;
|
|
56365
56384
|
logger;
|
|
56385
|
+
onEvict;
|
|
56366
56386
|
constructor(deps) {
|
|
56367
56387
|
this.store = deps.store;
|
|
56368
56388
|
this.logger = deps.logger;
|
|
56389
|
+
this.onEvict = deps.onEvict;
|
|
56369
56390
|
}
|
|
56370
56391
|
static async declare(store) {
|
|
56371
56392
|
await Promise.all([store.declareCollection.mutate({
|
|
@@ -56563,6 +56584,18 @@ var SummaryStore = class {
|
|
|
56563
56584
|
bytes: 0
|
|
56564
56585
|
}];
|
|
56565
56586
|
}
|
|
56587
|
+
/** The subset of `ids` that still have a row — the orphan audit's probe. Throws on a failed read. */
|
|
56588
|
+
async existingIds(ids) {
|
|
56589
|
+
const found = /* @__PURE__ */ new Set();
|
|
56590
|
+
await Promise.all(ids.map(async (id) => {
|
|
56591
|
+
const raw = await this.store.get.query({
|
|
56592
|
+
collection: SUMMARIES_COLLECTION,
|
|
56593
|
+
key: id
|
|
56594
|
+
});
|
|
56595
|
+
if (typeof raw === "object" && raw !== null) found.add(id);
|
|
56596
|
+
}));
|
|
56597
|
+
return found;
|
|
56598
|
+
}
|
|
56566
56599
|
/** Age sweep on the device window: everything opened before `cutoffMs`, members included. */
|
|
56567
56600
|
async evictBefore(deviceId, cutoffMs) {
|
|
56568
56601
|
const rows = await this.store.query.query({
|
|
@@ -56575,6 +56608,7 @@ var SummaryStore = class {
|
|
|
56575
56608
|
});
|
|
56576
56609
|
if (rows.length === 0) return 0;
|
|
56577
56610
|
const ids = rows.map((r) => r.id);
|
|
56611
|
+
await this.onEvict?.(ids);
|
|
56578
56612
|
await this.store.deleteWhere.mutate({
|
|
56579
56613
|
collection: SUMMARY_MEMBERS_COLLECTION,
|
|
56580
56614
|
filter: { whereIn: { summaryId: ids } }
|
|
@@ -59097,6 +59131,10 @@ function resolveMediaOwner(row) {
|
|
|
59097
59131
|
kind: "event",
|
|
59098
59132
|
eventId: ownerId
|
|
59099
59133
|
};
|
|
59134
|
+
case "summary": return {
|
|
59135
|
+
kind: "summary",
|
|
59136
|
+
summaryId: ownerId
|
|
59137
|
+
};
|
|
59100
59138
|
case "face": return ownerId.startsWith("face-") ? {
|
|
59101
59139
|
kind: "track",
|
|
59102
59140
|
trackId: ownerId.slice(FACE_MEDIA_OWNER_PREFIX.length)
|
|
@@ -59183,11 +59221,14 @@ var DEFAULT_ORPHAN_LIMITS = {
|
|
|
59183
59221
|
function questionsFor(refs) {
|
|
59184
59222
|
const trackIds = /* @__PURE__ */ new Set();
|
|
59185
59223
|
const eventIds = /* @__PURE__ */ new Set();
|
|
59224
|
+
const summaryIds = /* @__PURE__ */ new Set();
|
|
59186
59225
|
for (const ref of refs) if (ref.kind === "track") trackIds.add(ref.trackId);
|
|
59187
59226
|
else if (ref.kind === "event") eventIds.add(ref.eventId);
|
|
59227
|
+
else if (ref.kind === "summary") summaryIds.add(ref.summaryId);
|
|
59188
59228
|
return {
|
|
59189
59229
|
trackIds: [...trackIds],
|
|
59190
|
-
eventIds: [...eventIds]
|
|
59230
|
+
eventIds: [...eventIds],
|
|
59231
|
+
summaryIds: [...summaryIds]
|
|
59191
59232
|
};
|
|
59192
59233
|
}
|
|
59193
59234
|
/**
|
|
@@ -59219,12 +59260,14 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
59219
59260
|
break;
|
|
59220
59261
|
}
|
|
59221
59262
|
const refs = rows.map((row) => scope.resolve(row));
|
|
59222
|
-
const { trackIds, eventIds } = questionsFor(refs);
|
|
59263
|
+
const { trackIds, eventIds, summaryIds } = questionsFor(refs);
|
|
59223
59264
|
let liveTracks;
|
|
59224
59265
|
let liveEvents;
|
|
59266
|
+
let liveSummaries;
|
|
59225
59267
|
try {
|
|
59226
59268
|
liveTracks = trackIds.length > 0 ? await deps.liveTrackIds(trackIds) : /* @__PURE__ */ new Set();
|
|
59227
59269
|
liveEvents = eventIds.length > 0 ? await deps.liveEventIds(eventIds) : /* @__PURE__ */ new Set();
|
|
59270
|
+
liveSummaries = summaryIds.length > 0 ? await deps.liveSummaryIds(summaryIds) : /* @__PURE__ */ new Set();
|
|
59228
59271
|
} catch (err) {
|
|
59229
59272
|
deps.logger.warn("orphan audit: liveness probe failed — scope abandoned for this run", { meta: {
|
|
59230
59273
|
collection: scope.collection,
|
|
@@ -59248,7 +59291,7 @@ async function auditScope(scope, deps, limits, cursor) {
|
|
|
59248
59291
|
tally.unresolvable += 1;
|
|
59249
59292
|
continue;
|
|
59250
59293
|
}
|
|
59251
|
-
if (ref.kind === "track" ? liveTracks.has(ref.trackId) : ref.kind === "event" ? liveEvents.has(ref.eventId) : false) continue;
|
|
59294
|
+
if (ref.kind === "track" ? liveTracks.has(ref.trackId) : ref.kind === "event" ? liveEvents.has(ref.eventId) : ref.kind === "summary" ? liveSummaries.has(ref.summaryId) : false) continue;
|
|
59252
59295
|
const ts = num(row.data, scope.timestampField);
|
|
59253
59296
|
if (ts !== null && ts > graceFloor) {
|
|
59254
59297
|
tally.withinGrace += 1;
|
|
@@ -65200,6 +65243,196 @@ function rowFromMetadata(id, metadata) {
|
|
|
65200
65243
|
};
|
|
65201
65244
|
}
|
|
65202
65245
|
//#endregion
|
|
65246
|
+
//#region src/pipeline-analytics/summary/summary-score.ts
|
|
65247
|
+
/** A member whose own box is covered this much by others is hidden. */
|
|
65248
|
+
var OCCLUDED_COVER_FRACTION = .5;
|
|
65249
|
+
/** A box covering this share of the frame scores full marks on area. */
|
|
65250
|
+
var FULL_AREA_SHARE = .25;
|
|
65251
|
+
var WEIGHT_CONFIDENCE = .3;
|
|
65252
|
+
var WEIGHT_AREA = .25;
|
|
65253
|
+
var WEIGHT_RECOGNITION = .25;
|
|
65254
|
+
var WEIGHT_PLACEMENT = .2;
|
|
65255
|
+
function intersectionArea(a, b) {
|
|
65256
|
+
const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
|
|
65257
|
+
const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
|
|
65258
|
+
return w > 0 && h > 0 ? w * h : 0;
|
|
65259
|
+
}
|
|
65260
|
+
/**
|
|
65261
|
+
* Share of `own` covered by `others`, 0..1. Pairwise intersections summed and
|
|
65262
|
+
* capped at the own area: when two others overlap each other on top of this
|
|
65263
|
+
* box the sum over-counts, which only ever calls a member MORE hidden, never
|
|
65264
|
+
* less — the safe side for a shot meant to show everyone.
|
|
65265
|
+
*/
|
|
65266
|
+
function coveredFraction(own, others) {
|
|
65267
|
+
const area = own.width * own.height;
|
|
65268
|
+
if (area <= 0) return 1;
|
|
65269
|
+
const covered = others.reduce((sum, other) => sum + intersectionArea(own, other), 0);
|
|
65270
|
+
return Math.min(1, covered / area);
|
|
65271
|
+
}
|
|
65272
|
+
function clamp01(value) {
|
|
65273
|
+
return Math.max(0, Math.min(1, value));
|
|
65274
|
+
}
|
|
65275
|
+
function placement(box, frameWidth, frameHeight) {
|
|
65276
|
+
const cx = box.x + box.width / 2;
|
|
65277
|
+
const cy = box.y + box.height / 2;
|
|
65278
|
+
const dx = cx - frameWidth / 2;
|
|
65279
|
+
const dy = cy - frameHeight / 2;
|
|
65280
|
+
const halfDiagonal = Math.hypot(frameWidth, frameHeight) / 2;
|
|
65281
|
+
return halfDiagonal <= 0 ? 0 : 1 - clamp01(Math.hypot(dx, dy) / halfDiagonal);
|
|
65282
|
+
}
|
|
65283
|
+
function memberQuality(d, frameWidth, frameHeight) {
|
|
65284
|
+
const frameArea = frameWidth * frameHeight;
|
|
65285
|
+
const cappedArea = clamp01((frameArea <= 0 ? 0 : d.bbox.width * d.bbox.height / frameArea) / FULL_AREA_SHARE);
|
|
65286
|
+
return WEIGHT_CONFIDENCE * clamp01(d.confidence) + WEIGHT_AREA * cappedArea + WEIGHT_RECOGNITION * (d.recognized ? 1 : 0) + WEIGHT_PLACEMENT * placement(d.bbox, frameWidth, frameHeight);
|
|
65287
|
+
}
|
|
65288
|
+
/** Score one frame for one summary. Pure; never throws on empty input. */
|
|
65289
|
+
function scoreSummaryFrame(input) {
|
|
65290
|
+
const present = input.detections.filter((d) => input.members.has(d.trackId));
|
|
65291
|
+
const visible = present.filter((d) => coveredFraction(d.bbox, present.filter((o) => o !== d).map((o) => o.bbox)) < OCCLUDED_COVER_FRACTION);
|
|
65292
|
+
if (visible.length === 0) return {
|
|
65293
|
+
visible: 0,
|
|
65294
|
+
quality: 0
|
|
65295
|
+
};
|
|
65296
|
+
const quality = visible.reduce((sum, d) => sum + memberQuality(d, input.frameWidth, input.frameHeight), 0) / visible.length;
|
|
65297
|
+
return {
|
|
65298
|
+
visible: visible.length,
|
|
65299
|
+
quality
|
|
65300
|
+
};
|
|
65301
|
+
}
|
|
65302
|
+
/**
|
|
65303
|
+
* Lexicographic: more visible members wins outright; only equal counts fall
|
|
65304
|
+
* through to quality. Positive when `a` is the better shot.
|
|
65305
|
+
*/
|
|
65306
|
+
function compareSummaryScores(a, b) {
|
|
65307
|
+
if (a.visible !== b.visible) return a.visible - b.visible;
|
|
65308
|
+
return a.quality - b.quality;
|
|
65309
|
+
}
|
|
65310
|
+
/** A candidate replaces the best only when it is STRICTLY better — a tie keeps
|
|
65311
|
+
* the pixels already materialised (they cost a native round trip). */
|
|
65312
|
+
function isBetterSummaryScore(candidate, best) {
|
|
65313
|
+
if (candidate.visible === 0) return false;
|
|
65314
|
+
return best === null || compareSummaryScores(candidate, best) > 0;
|
|
65315
|
+
}
|
|
65316
|
+
//#endregion
|
|
65317
|
+
//#region src/pipeline-analytics/summary/group-shot.ts
|
|
65318
|
+
/**
|
|
65319
|
+
* The running argmax of one open summary's group shot.
|
|
65320
|
+
*
|
|
65321
|
+
* RAM only, on purpose: it decides WHEN pixels are worth a native round trip
|
|
65322
|
+
* (only when the maximum improves — D9/D18), it is not the record of the
|
|
65323
|
+
* shot. The record is the media row the caller writes under the summary's
|
|
65324
|
+
* id; after a restart an open summary simply starts its argmax again and
|
|
65325
|
+
* replaces the row the next time it beats what it can remember, which is
|
|
65326
|
+
* nothing. That is a weaker shot for one session, never a wrong one.
|
|
65327
|
+
*/
|
|
65328
|
+
var GroupShotTracker = class {
|
|
65329
|
+
best = /* @__PURE__ */ new Map();
|
|
65330
|
+
/**
|
|
65331
|
+
* Offer a frame. Returns `true` when it beats the summary's best so far —
|
|
65332
|
+
* the caller's cue to materialise the pixels for THIS frame, now, while its
|
|
65333
|
+
* handle is still leased.
|
|
65334
|
+
*/
|
|
65335
|
+
offer(summaryId, score, frame) {
|
|
65336
|
+
if (!isBetterSummaryScore(score, (this.best.get(summaryId) ?? null)?.score ?? null)) return false;
|
|
65337
|
+
this.best.set(summaryId, {
|
|
65338
|
+
score,
|
|
65339
|
+
frame
|
|
65340
|
+
});
|
|
65341
|
+
return true;
|
|
65342
|
+
}
|
|
65343
|
+
bestOf(summaryId) {
|
|
65344
|
+
return this.best.get(summaryId) ?? null;
|
|
65345
|
+
}
|
|
65346
|
+
/** A sealed or evicted summary takes no more frames; drop its state. */
|
|
65347
|
+
forget(summaryId) {
|
|
65348
|
+
this.best.delete(summaryId);
|
|
65349
|
+
}
|
|
65350
|
+
get size() {
|
|
65351
|
+
return this.best.size;
|
|
65352
|
+
}
|
|
65353
|
+
};
|
|
65354
|
+
//#endregion
|
|
65355
|
+
//#region src/pipeline-analytics/summary/group-shot-materialiser.ts
|
|
65356
|
+
async function persistSummaryGroupShot(deps, input) {
|
|
65357
|
+
const { deviceId, summaryId, frameHandle } = input;
|
|
65358
|
+
const drop = {
|
|
65359
|
+
tags: { deviceId },
|
|
65360
|
+
meta: {
|
|
65361
|
+
summaryId,
|
|
65362
|
+
nodeId: frameHandle.nodeId
|
|
65363
|
+
}
|
|
65364
|
+
};
|
|
65365
|
+
try {
|
|
65366
|
+
const result = await deps.fetchNativeFullFrame(frameHandle, input.maxWidth);
|
|
65367
|
+
const native = result?.frame;
|
|
65368
|
+
if (!native || native.format !== "rgb" || native.width <= 0 || native.height <= 0) {
|
|
65369
|
+
deps.logger.warn("group shot native miss — the previous shot stands", {
|
|
65370
|
+
...drop,
|
|
65371
|
+
meta: {
|
|
65372
|
+
...drop.meta,
|
|
65373
|
+
shmId: frameHandle.shmId
|
|
65374
|
+
}
|
|
65375
|
+
});
|
|
65376
|
+
return { kind: "native-miss" };
|
|
65377
|
+
}
|
|
65378
|
+
if (!keyFrameAcceptsTier(result.tier)) {
|
|
65379
|
+
deps.logger.warn("group shot RAM tier rejected — the previous shot stands", {
|
|
65380
|
+
...drop,
|
|
65381
|
+
meta: {
|
|
65382
|
+
...drop.meta,
|
|
65383
|
+
shmId: frameHandle.shmId,
|
|
65384
|
+
tier: result.tier
|
|
65385
|
+
}
|
|
65386
|
+
});
|
|
65387
|
+
return { kind: "ram-tier" };
|
|
65388
|
+
}
|
|
65389
|
+
const { keyFrame, keyFrameSmall } = await deps.encodeVariants(native);
|
|
65390
|
+
const keyFrameKey = await deps.media.putReplacing({
|
|
65391
|
+
deviceId,
|
|
65392
|
+
ownerKind: "summary",
|
|
65393
|
+
ownerId: summaryId,
|
|
65394
|
+
kind: "keyFrame",
|
|
65395
|
+
timestamp: input.timestamp,
|
|
65396
|
+
data: keyFrame
|
|
65397
|
+
});
|
|
65398
|
+
try {
|
|
65399
|
+
await deps.media.putReplacing({
|
|
65400
|
+
deviceId,
|
|
65401
|
+
ownerKind: "summary",
|
|
65402
|
+
ownerId: summaryId,
|
|
65403
|
+
kind: "keyFrameSmall",
|
|
65404
|
+
timestamp: input.timestamp,
|
|
65405
|
+
data: keyFrameSmall
|
|
65406
|
+
});
|
|
65407
|
+
} catch (err) {
|
|
65408
|
+
deps.logger.warn("group shot web companion write failed — the native shot is stored", {
|
|
65409
|
+
...drop,
|
|
65410
|
+
meta: {
|
|
65411
|
+
...drop.meta,
|
|
65412
|
+
error: errMsg$1(err)
|
|
65413
|
+
}
|
|
65414
|
+
});
|
|
65415
|
+
}
|
|
65416
|
+
return {
|
|
65417
|
+
kind: "landed",
|
|
65418
|
+
keyFrameKey
|
|
65419
|
+
};
|
|
65420
|
+
} catch (err) {
|
|
65421
|
+
const error = errMsg$1(err);
|
|
65422
|
+
deps.logger.warn("group shot capture failed", {
|
|
65423
|
+
...drop,
|
|
65424
|
+
meta: {
|
|
65425
|
+
...drop.meta,
|
|
65426
|
+
error
|
|
65427
|
+
}
|
|
65428
|
+
});
|
|
65429
|
+
return {
|
|
65430
|
+
kind: "error",
|
|
65431
|
+
error
|
|
65432
|
+
};
|
|
65433
|
+
}
|
|
65434
|
+
}
|
|
65435
|
+
//#endregion
|
|
65203
65436
|
//#region src/pipeline-analytics/summary/summary-sealer.ts
|
|
65204
65437
|
function errMsg(err) {
|
|
65205
65438
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -65207,16 +65440,33 @@ function errMsg(err) {
|
|
|
65207
65440
|
var SummarySealer = class {
|
|
65208
65441
|
deps;
|
|
65209
65442
|
sweepInFlight = false;
|
|
65443
|
+
/**
|
|
65444
|
+
* What the FRAME path may ask at frame-rate: the open summary of a camera
|
|
65445
|
+
* and the members it has seen THIS process open. Fed only by this class's
|
|
65446
|
+
* own writes, never the authority (D224): a fresh process answers `null`
|
|
65447
|
+
* until a track opens, and a summary open across a restart is scored on the
|
|
65448
|
+
* members that open after it — a weaker shot, never a wrong one.
|
|
65449
|
+
*/
|
|
65450
|
+
openMirror = /* @__PURE__ */ new Map();
|
|
65210
65451
|
constructor(deps) {
|
|
65211
65452
|
this.deps = deps;
|
|
65212
65453
|
}
|
|
65454
|
+
openFor(deviceId) {
|
|
65455
|
+
return this.openMirror.get(deviceId) ?? null;
|
|
65456
|
+
}
|
|
65213
65457
|
async onTrackOpened(input) {
|
|
65214
65458
|
try {
|
|
65215
|
-
await this.deps.summaries.openOrAbsorb({
|
|
65459
|
+
const row = await this.deps.summaries.openOrAbsorb({
|
|
65216
65460
|
deviceId: input.deviceId,
|
|
65217
65461
|
trackId: input.trackId,
|
|
65218
65462
|
at: this.deps.now()
|
|
65219
65463
|
});
|
|
65464
|
+
const mirror = this.openMirror.get(input.deviceId);
|
|
65465
|
+
if (mirror !== void 0 && mirror.id === row.id) mirror.members.add(input.trackId);
|
|
65466
|
+
else this.openMirror.set(input.deviceId, {
|
|
65467
|
+
id: row.id,
|
|
65468
|
+
members: new Set([input.trackId])
|
|
65469
|
+
});
|
|
65220
65470
|
} catch (err) {
|
|
65221
65471
|
this.deps.logger.warn("summary: could not open or absorb for a track", {
|
|
65222
65472
|
tags: { deviceId: input.deviceId },
|
|
@@ -65272,13 +65522,19 @@ var SummarySealer = class {
|
|
|
65272
65522
|
if (!quiet) return;
|
|
65273
65523
|
const sealedAt = this.deps.now();
|
|
65274
65524
|
try {
|
|
65275
|
-
if (await this.deps.summaries.seal(summary, sealedAt))
|
|
65276
|
-
|
|
65277
|
-
|
|
65278
|
-
|
|
65279
|
-
|
|
65280
|
-
|
|
65281
|
-
|
|
65525
|
+
if (await this.deps.summaries.seal(summary, sealedAt)) {
|
|
65526
|
+
const mirror = this.openMirror.get(deviceId);
|
|
65527
|
+
const members = mirror?.id === summaryId ? mirror.members : /* @__PURE__ */ new Set();
|
|
65528
|
+
if (mirror?.id === summaryId) this.openMirror.delete(deviceId);
|
|
65529
|
+
this.deps.onSealed?.(summaryId, members);
|
|
65530
|
+
this.deps.logger.info("summary sealed", {
|
|
65531
|
+
tags: { deviceId },
|
|
65532
|
+
meta: {
|
|
65533
|
+
summaryId,
|
|
65534
|
+
sealedAt
|
|
65535
|
+
}
|
|
65536
|
+
});
|
|
65537
|
+
}
|
|
65282
65538
|
} catch (err) {
|
|
65283
65539
|
this.deps.logger.warn("summary sweep: seal failed — the row stays open for the next sweep", {
|
|
65284
65540
|
tags: { deviceId },
|
|
@@ -66607,6 +66863,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
66607
66863
|
summaryStore = null;
|
|
66608
66864
|
/** The ONLY writer of a summary's lifecycle (D359). */
|
|
66609
66865
|
summarySealer = null;
|
|
66866
|
+
/** The running argmax of every open summary's group shot (D359 phase 3). RAM only. */
|
|
66867
|
+
groupShots = new GroupShotTracker();
|
|
66868
|
+
/**
|
|
66869
|
+
* Tracks a gallery match has named, for the group-shot score's recognition
|
|
66870
|
+
* term. "Recognised so far" rather than "at that instant": the match arrives
|
|
66871
|
+
* on the detail plane, after the frame; the frames that follow carry it.
|
|
66872
|
+
* Pruned when the track's summary seals, so it is bounded by open summaries.
|
|
66873
|
+
*/
|
|
66874
|
+
recognizedTrackIds = /* @__PURE__ */ new Set();
|
|
66610
66875
|
/** Ingest-side reverse index (sensor device → linked camera ids), TTL-cached
|
|
66611
66876
|
* so the DeviceStateChanged handler stays cheap. */
|
|
66612
66877
|
linkedCamerasCache = null;
|
|
@@ -68109,14 +68374,21 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68109
68374
|
});
|
|
68110
68375
|
this.summaryStore = new SummaryStore({
|
|
68111
68376
|
store: api.settingsStore,
|
|
68112
|
-
logger: this.ctx.logger.child("summary")
|
|
68377
|
+
logger: this.ctx.logger.child("summary"),
|
|
68378
|
+
onEvict: async (ids) => {
|
|
68379
|
+
await this.mediaStore?.deleteForOwner("summary", ids);
|
|
68380
|
+
}
|
|
68113
68381
|
});
|
|
68114
68382
|
this.summarySealer = new SummarySealer({
|
|
68115
68383
|
summaries: this.summaryStore,
|
|
68116
68384
|
openTrackIds: async (deviceId) => this.residents.trackIdsForDevice(deviceId),
|
|
68117
68385
|
isCameraQuiet: async (deviceId) => (this.stationaryRegistry?.count(deviceId) ?? 0) === 0,
|
|
68118
68386
|
now: () => Date.now(),
|
|
68119
|
-
logger: this.ctx.logger.child("summary-sealer")
|
|
68387
|
+
logger: this.ctx.logger.child("summary-sealer"),
|
|
68388
|
+
onSealed: (summaryId, members) => {
|
|
68389
|
+
this.groupShots.forget(summaryId);
|
|
68390
|
+
for (const trackId of members) this.recognizedTrackIds.delete(trackId);
|
|
68391
|
+
}
|
|
68120
68392
|
});
|
|
68121
68393
|
this.packageDropDetector = new PackageDropDetector({
|
|
68122
68394
|
events: eventStore,
|
|
@@ -68965,6 +69237,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68965
69237
|
this.eventMediaDataPlane = await this.ctx.dataPlane?.serve({
|
|
68966
69238
|
prefix: "event-media",
|
|
68967
69239
|
access: planeAccess("event-media"),
|
|
69240
|
+
share: ["events-view"],
|
|
68968
69241
|
handler
|
|
68969
69242
|
}) ?? null;
|
|
68970
69243
|
this.eventMediaBaseUrl = this.eventMediaDataPlane !== null ? `/addon/${this.ctx.id}/event-media` : null;
|
|
@@ -70551,6 +70824,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70551
70824
|
for (const crops of childCropsByEvent.values()) for (const c of crops) if (c.kind === "faceCrop") faceCrops += 1;
|
|
70552
70825
|
else plateCrops += 1;
|
|
70553
70826
|
const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight);
|
|
70827
|
+
this.offerGroupShot(deviceId, result, frameHandle);
|
|
70554
70828
|
for (const retry of this.collectFirstFrameRetries(deviceId, result.tracked, result.timestamp, firstFrameTargets)) firstFrameTargets.push(retry);
|
|
70555
70829
|
const targetedThisFrame = new Set([...firstFrameTargets.map((t) => t.trackId), ...snapshotTargets.map((s) => s.trackId)]);
|
|
70556
70830
|
const rasterFallbackCandidates = [];
|
|
@@ -71433,6 +71707,63 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71433
71707
|
* is the scheduling (coalescing, priority, budget) and the bookkeeping the
|
|
71434
71708
|
* retry gate reads.
|
|
71435
71709
|
*/
|
|
71710
|
+
/**
|
|
71711
|
+
* Offer one processed frame to the camera's OPEN summary (D359 phase 3).
|
|
71712
|
+
* Metadata only, at frame-rate: the frame is scored against the members the
|
|
71713
|
+
* sealer has mirrored, and the pixels are fetched — by handle, at the owning
|
|
71714
|
+
* node, as a native key frame — only when this frame beats the best so far.
|
|
71715
|
+
*/
|
|
71716
|
+
offerGroupShot(deviceId, result, frameHandle) {
|
|
71717
|
+
const open = this.summarySealer?.openFor(deviceId);
|
|
71718
|
+
if (!open || frameHandle === void 0) return;
|
|
71719
|
+
const score = scoreSummaryFrame({
|
|
71720
|
+
members: open.members,
|
|
71721
|
+
frameWidth: result.frameWidth,
|
|
71722
|
+
frameHeight: result.frameHeight,
|
|
71723
|
+
detections: result.tracked.map((t) => ({
|
|
71724
|
+
trackId: t.trackId,
|
|
71725
|
+
bbox: {
|
|
71726
|
+
x: t.bbox.x,
|
|
71727
|
+
y: t.bbox.y,
|
|
71728
|
+
width: t.bbox.w,
|
|
71729
|
+
height: t.bbox.h
|
|
71730
|
+
},
|
|
71731
|
+
confidence: t.confidence,
|
|
71732
|
+
recognized: this.recognizedTrackIds.has(t.trackId)
|
|
71733
|
+
}))
|
|
71734
|
+
});
|
|
71735
|
+
if (!this.groupShots.offer(open.id, score, { timestamp: result.timestamp })) return;
|
|
71736
|
+
const getNative = this.getNativeKeyFrameRgb;
|
|
71737
|
+
const mediaStore = this.mediaStore;
|
|
71738
|
+
if (!getNative || !mediaStore) return;
|
|
71739
|
+
const summaryId = open.id;
|
|
71740
|
+
this.captureScheduler.request({
|
|
71741
|
+
deviceId,
|
|
71742
|
+
kind: "keyFrame",
|
|
71743
|
+
coalesceKey: `summary:${summaryId}:keyFrame`,
|
|
71744
|
+
exec: () => persistSummaryGroupShot({
|
|
71745
|
+
fetchNativeFullFrame: getNative,
|
|
71746
|
+
encodeVariants: encodeKeyFrameVariants,
|
|
71747
|
+
media: mediaStore,
|
|
71748
|
+
logger: this.ctx.logger
|
|
71749
|
+
}, {
|
|
71750
|
+
deviceId,
|
|
71751
|
+
summaryId,
|
|
71752
|
+
timestamp: result.timestamp,
|
|
71753
|
+
frameHandle,
|
|
71754
|
+
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH
|
|
71755
|
+
})
|
|
71756
|
+
}).then((outcome) => {
|
|
71757
|
+
if (outcome?.kind === "landed") this.ctx.logger.debug("summary group shot landed", {
|
|
71758
|
+
tags: { deviceId },
|
|
71759
|
+
meta: {
|
|
71760
|
+
summaryId,
|
|
71761
|
+
visible: score.visible,
|
|
71762
|
+
quality: Number(score.quality.toFixed(3))
|
|
71763
|
+
}
|
|
71764
|
+
});
|
|
71765
|
+
}).catch(() => {});
|
|
71766
|
+
}
|
|
71436
71767
|
async persistKeyFrames(deviceId, timestamp, captures, frameHandle) {
|
|
71437
71768
|
const getNative = this.getNativeKeyFrameRgb;
|
|
71438
71769
|
const mediaStore = this.mediaStore;
|
|
@@ -71577,6 +71908,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71577
71908
|
* FaceRecognizer's per-frame auto-match, and `routeDetailResults`' plate /
|
|
71578
71909
|
* fine-classification attribution. */
|
|
71579
71910
|
promoteSemanticRecognition(input) {
|
|
71911
|
+
this.recognizedTrackIds.add(input.trackId);
|
|
71580
71912
|
const ctx = this.ctxIfReady;
|
|
71581
71913
|
if (ctx === null) return;
|
|
71582
71914
|
promoteSemanticBestFrame({
|
|
@@ -73815,6 +74147,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
73815
74147
|
},
|
|
73816
74148
|
liveTrackIds: (ids) => trackStore.existingTrackIds(ids),
|
|
73817
74149
|
liveEventIds: (ids) => eventStore.existingEventIds(ids),
|
|
74150
|
+
liveSummaryIds: (ids) => {
|
|
74151
|
+
const summaries = this.summaryStore;
|
|
74152
|
+
if (!summaries) throw new Error("summary store not built — probe unavailable");
|
|
74153
|
+
return summaries.existingIds(ids);
|
|
74154
|
+
},
|
|
73818
74155
|
reclaim: async (collection, key) => {
|
|
73819
74156
|
if (collection === "pipeline-analytics:media") {
|
|
73820
74157
|
await mediaStore.deleteByKey(key);
|
|
@@ -75449,7 +75786,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75449
75786
|
if (!file) return null;
|
|
75450
75787
|
return {
|
|
75451
75788
|
bytes: Buffer.from(file.base64, "base64"),
|
|
75452
|
-
key: file.key
|
|
75789
|
+
key: file.key,
|
|
75790
|
+
deviceId: file.deviceId
|
|
75453
75791
|
};
|
|
75454
75792
|
}
|
|
75455
75793
|
/**
|
|
@@ -75463,7 +75801,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75463
75801
|
try {
|
|
75464
75802
|
return {
|
|
75465
75803
|
bytes: await makeSquareThumb(media.bytes, variant.size),
|
|
75466
|
-
key: `${media.key}|t${variant.size}
|
|
75804
|
+
key: `${media.key}|t${variant.size}`,
|
|
75805
|
+
deviceId: media.deviceId
|
|
75467
75806
|
};
|
|
75468
75807
|
} catch (err) {
|
|
75469
75808
|
this.ctx.logger.debug("event media: thumb variant failed — serving full", { meta: {
|
|
@@ -75493,7 +75832,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75493
75832
|
const file = await (this.mediaStore?.getByKey(key) ?? Promise.resolve(null));
|
|
75494
75833
|
return file === null ? null : {
|
|
75495
75834
|
bytes: Buffer.from(file.base64, "base64"),
|
|
75496
|
-
key: file.key
|
|
75835
|
+
key: file.key,
|
|
75836
|
+
deviceId: file.deviceId
|
|
75497
75837
|
};
|
|
75498
75838
|
};
|
|
75499
75839
|
const eventFiles = await listInfo("event", id);
|