@camstack/addon-post-analysis 1.2.78 → 1.2.80
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-CFOT1Yoz.js → dist-BTbbBBpS.js} +227 -5
- package/dist/{dist-CA2KdMY9.mjs → dist-D9Z7czrz.mjs} +227 -5
- 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-DteUZxxp.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DHm8hb9G.mjs} +2 -2
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B4LMjxKe.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BeCNd7Q3.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-BTue_6nN.mjs → hostInit-Dtf-oVL7.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +265 -55
- package/dist/pipeline-analytics/index.mjs +265 -55
- 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-BTbbBBpS.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");
|
|
@@ -14144,12 +14144,15 @@ function dwellSeconds(candidate) {
|
|
|
14144
14144
|
* branch that discards work silently reads as "it never happened".
|
|
14145
14145
|
*/
|
|
14146
14146
|
function filterSummaryCandidates(candidates, filters, zoneOwner) {
|
|
14147
|
+
const moving = filters?.includeStationary === true ? candidates : candidates.filter((candidate) => candidate.stationary !== true);
|
|
14148
|
+
const droppedStationary = candidates.length - moving.length;
|
|
14147
14149
|
if (filters === void 0) return {
|
|
14148
|
-
kept: [...
|
|
14150
|
+
kept: [...moving],
|
|
14149
14151
|
droppedByClass: 0,
|
|
14150
14152
|
droppedByZone: 0,
|
|
14151
14153
|
droppedByImportance: 0,
|
|
14152
|
-
droppedByDwell: 0
|
|
14154
|
+
droppedByDwell: 0,
|
|
14155
|
+
droppedStationary
|
|
14153
14156
|
};
|
|
14154
14157
|
const include = filters.classes;
|
|
14155
14158
|
const exclude = new Set(filters.classesExclude ?? []);
|
|
@@ -14158,7 +14161,7 @@ function filterSummaryCandidates(candidates, filters, zoneOwner) {
|
|
|
14158
14161
|
let droppedByZone = 0;
|
|
14159
14162
|
let droppedByImportance = 0;
|
|
14160
14163
|
let droppedByDwell = 0;
|
|
14161
|
-
for (const candidate of
|
|
14164
|
+
for (const candidate of moving) {
|
|
14162
14165
|
const own = classesOf(candidate);
|
|
14163
14166
|
if (own.some((c) => exclude.has(c))) {
|
|
14164
14167
|
droppedByClass += 1;
|
|
@@ -14194,7 +14197,8 @@ function filterSummaryCandidates(candidates, filters, zoneOwner) {
|
|
|
14194
14197
|
droppedByClass,
|
|
14195
14198
|
droppedByZone,
|
|
14196
14199
|
droppedByImportance,
|
|
14197
|
-
droppedByDwell
|
|
14200
|
+
droppedByDwell,
|
|
14201
|
+
droppedStationary
|
|
14198
14202
|
};
|
|
14199
14203
|
}
|
|
14200
14204
|
/**
|
|
@@ -14569,7 +14573,8 @@ var NcSummaryProducer = class {
|
|
|
14569
14573
|
droppedByClass: filtered.droppedByClass,
|
|
14570
14574
|
droppedByZone: filtered.droppedByZone,
|
|
14571
14575
|
droppedByImportance: filtered.droppedByImportance,
|
|
14572
|
-
droppedByDwell: filtered.droppedByDwell
|
|
14576
|
+
droppedByDwell: filtered.droppedByDwell,
|
|
14577
|
+
droppedStationary: filtered.droppedStationary
|
|
14573
14578
|
} });
|
|
14574
14579
|
if (opts.stamp) await this.deps.store.markGenerated(rule.id, window.endMs);
|
|
14575
14580
|
return {
|
|
@@ -14577,7 +14582,8 @@ var NcSummaryProducer = class {
|
|
|
14577
14582
|
window,
|
|
14578
14583
|
tiles: 0,
|
|
14579
14584
|
cameras: 0,
|
|
14580
|
-
matched: 0
|
|
14585
|
+
matched: 0,
|
|
14586
|
+
droppedStationary: filtered.droppedStationary
|
|
14581
14587
|
};
|
|
14582
14588
|
}
|
|
14583
14589
|
const loaded = await this.loadTiles(rule, selection.tiles);
|
|
@@ -14600,6 +14606,7 @@ var NcSummaryProducer = class {
|
|
|
14600
14606
|
produced: 0,
|
|
14601
14607
|
window,
|
|
14602
14608
|
tiles: loaded.length,
|
|
14609
|
+
droppedStationary: filtered.droppedStationary,
|
|
14603
14610
|
skippedByAi: true,
|
|
14604
14611
|
...ai.aiFailure !== void 0 ? { aiFailure: ai.aiFailure } : {},
|
|
14605
14612
|
...ai.aiImages !== void 0 ? { aiImages: ai.aiImages } : {}
|
|
@@ -14630,6 +14637,8 @@ var NcSummaryProducer = class {
|
|
|
14630
14637
|
...meta,
|
|
14631
14638
|
scanned: candidates.length,
|
|
14632
14639
|
matched: filtered.kept.length,
|
|
14640
|
+
droppedStationary: filtered.droppedStationary,
|
|
14641
|
+
droppedByClass: filtered.droppedByClass,
|
|
14633
14642
|
tiles: loaded.length,
|
|
14634
14643
|
cameras: new Set(loaded.map((t) => t.candidate.deviceId)).size,
|
|
14635
14644
|
camerasDropped: selection.camerasDropped,
|
|
@@ -14648,6 +14657,7 @@ var NcSummaryProducer = class {
|
|
|
14648
14657
|
tiles: loaded.length,
|
|
14649
14658
|
cameras: new Set(loaded.map((t) => t.candidate.deviceId)).size,
|
|
14650
14659
|
matched: filtered.kept.length,
|
|
14660
|
+
droppedStationary: filtered.droppedStationary,
|
|
14651
14661
|
enqueued,
|
|
14652
14662
|
...mosaic.bytes > 0 ? { mosaicBytes: mosaic.bytes } : {},
|
|
14653
14663
|
...mosaic.ref !== null ? { mosaicUrl: mosaic.ref.url } : {},
|
|
@@ -15020,7 +15030,26 @@ var NcSummaryFiltersSchema = require_dist.object({
|
|
|
15020
15030
|
classesExclude: require_dist.array(require_dist.string().min(1).max(40)).max(20).optional(),
|
|
15021
15031
|
zones: require_dist.array(require_dist.string()).max(40).optional(),
|
|
15022
15032
|
minImportance: require_dist.number().min(0).max(1).optional(),
|
|
15023
|
-
minDwellSeconds: require_dist.number().min(0).max(3600).optional()
|
|
15033
|
+
minDwellSeconds: require_dist.number().min(0).max(3600).optional(),
|
|
15034
|
+
/**
|
|
15035
|
+
* Put PARKED objects in the digest too. Off unless explicitly turned on, and
|
|
15036
|
+
* an ABSENT field reads as off — the soft migration, no backfill.
|
|
15037
|
+
*
|
|
15038
|
+
* A digest is a record of PASSAGES: the operator's words are "the last
|
|
15039
|
+
* summaries still include an enormous number of stationary tracks". Since
|
|
15040
|
+
* D161 a track promoted to the stationary registry is persisted rather than
|
|
15041
|
+
* dropped (it owns media the retention sweep has to reach), so the car parked
|
|
15042
|
+
* in front of camera 617 now enters every window it overlaps and, ranked on
|
|
15043
|
+
* importance and dwell, wins tiles from the passages the digest is about. The
|
|
15044
|
+
* measured effect on the live hub was 1-2 tiles becoming 10.
|
|
15045
|
+
*
|
|
15046
|
+
* `.optional()` and NOT `.default(false)`: a `.default()` here would be
|
|
15047
|
+
* materialised by `NcSummaryRulePatchSchema` on any patch that named
|
|
15048
|
+
* `filters`, which is the `.partial()` trap this module's docblock describes.
|
|
15049
|
+
* `filterSummaryCandidates` reads `=== true`, so absent and false are the
|
|
15050
|
+
* same answer and neither needs a stored value.
|
|
15051
|
+
*/
|
|
15052
|
+
includeStationary: require_dist.boolean().optional()
|
|
15024
15053
|
});
|
|
15025
15054
|
/**
|
|
15026
15055
|
* How tiles are chosen when the window holds more tracks than the budget.
|
|
@@ -21819,6 +21848,14 @@ var ncActions = require_dist.defineCustomActions({
|
|
|
21819
21848
|
tiles: require_dist.number().int().optional(),
|
|
21820
21849
|
cameras: require_dist.number().int().optional(),
|
|
21821
21850
|
matched: require_dist.number().int().optional(),
|
|
21851
|
+
/**
|
|
21852
|
+
* PARKED objects the window held and the rule dropped (`filters
|
|
21853
|
+
* .includeStationary`). Without it a Test that returns two tiles out of
|
|
21854
|
+
* forty tracks cannot tell the operator whether the night was quiet or
|
|
21855
|
+
* whether it was the car park — and that is the exact question this
|
|
21856
|
+
* option exists to answer.
|
|
21857
|
+
*/
|
|
21858
|
+
droppedStationary: require_dist.number().int().optional(),
|
|
21822
21859
|
enqueued: require_dist.number().int().optional(),
|
|
21823
21860
|
mosaicBytes: require_dist.number().int().optional(),
|
|
21824
21861
|
mosaicUrl: require_dist.string().optional(),
|
|
@@ -22285,6 +22322,7 @@ function makeNcActionHandlers(deps) {
|
|
|
22285
22322
|
...out.tiles !== void 0 ? { tiles: out.tiles } : {},
|
|
22286
22323
|
...out.cameras !== void 0 ? { cameras: out.cameras } : {},
|
|
22287
22324
|
...out.matched !== void 0 ? { matched: out.matched } : {},
|
|
22325
|
+
...out.droppedStationary !== void 0 ? { droppedStationary: out.droppedStationary } : {},
|
|
22288
22326
|
...out.enqueued !== void 0 ? { enqueued: out.enqueued } : {},
|
|
22289
22327
|
...out.mosaicBytes !== void 0 ? { mosaicBytes: out.mosaicBytes } : {},
|
|
22290
22328
|
...out.mosaicUrl !== void 0 ? { mosaicUrl: out.mosaicUrl } : {},
|
|
@@ -26288,7 +26326,9 @@ var FrameProcessor = class {
|
|
|
26288
26326
|
}) : {
|
|
26289
26327
|
suppressedIndices: /* @__PURE__ */ new Set(),
|
|
26290
26328
|
confirmed: [],
|
|
26291
|
-
wokenEntryIds: []
|
|
26329
|
+
wokenEntryIds: [],
|
|
26330
|
+
armedEntryIds: [],
|
|
26331
|
+
disarmedEntryIds: []
|
|
26292
26332
|
};
|
|
26293
26333
|
const trackerInput = gate.suppressedIndices.size > 0 ? filteredDetections.filter((_, i) => !gate.suppressedIndices.has(i)) : filteredDetections;
|
|
26294
26334
|
const trackedDetections = this.tracker.update(trackerInput, timestamp, {
|
|
@@ -26498,6 +26538,8 @@ var FrameProcessor = class {
|
|
|
26498
26538
|
rawTrackedDetections: trackedDetections,
|
|
26499
26539
|
stationaryConfirmed: gate.confirmed,
|
|
26500
26540
|
stationaryWoken: gate.wokenEntryIds,
|
|
26541
|
+
stationaryArmed: gate.armedEntryIds,
|
|
26542
|
+
stationaryDisarmed: gate.disarmedEntryIds,
|
|
26501
26543
|
riderFolds
|
|
26502
26544
|
};
|
|
26503
26545
|
}
|
|
@@ -36062,20 +36104,38 @@ function centroidInside(b, p) {
|
|
|
36062
36104
|
* box → SUPPRESS (detector box jitter — the box shrank/grew around the same
|
|
36063
36105
|
* centre; still the parked object, must not spawn a duplicate track).
|
|
36064
36106
|
* - `wakeAssocIou` ≤ best IoU < `suppressIou`, centroid OUTSIDE the parked box
|
|
36065
|
-
* →
|
|
36066
|
-
* suppressed, so it spawns a fresh moving track.
|
|
36107
|
+
* → DEPARTING (the object's box slid off its spot).
|
|
36067
36108
|
* - best IoU < `wakeAssocIou` → MISS: no detection overlaps this entry (a
|
|
36068
|
-
* different object, or the parked object is momentarily undetected).
|
|
36069
|
-
*
|
|
36109
|
+
* different object, or the parked object is momentarily undetected). The TTL
|
|
36110
|
+
* sweep retires it only if the absence persists.
|
|
36111
|
+
*
|
|
36112
|
+
* ## A departure is never acted on from ONE read (D49)
|
|
36113
|
+
*
|
|
36114
|
+
* Retiring an entry DESTROYS work: the object's registry identity, and with it
|
|
36115
|
+
* the suppression that stops it re-spawning a track every 30 s. So a single
|
|
36116
|
+
* departing read only ARMS the entry — the detection is still suppressed and
|
|
36117
|
+
* the entry survives. The entry is retired only when a SECOND read agrees:
|
|
36118
|
+
* departing again, or missing entirely (the object really has left the spot).
|
|
36119
|
+
* A confirming read DISARMS it.
|
|
36120
|
+
*
|
|
36121
|
+
* The caller owns the armed set and passes it back in; this function stays
|
|
36122
|
+
* pure. Cost of the extra read: a genuinely departing object spawns its track
|
|
36123
|
+
* one frame later. Cost of NOT having it, measured on the live hub over 24 h:
|
|
36124
|
+
* 65% of all stationary entries were retired within 300 s — below the TTL, so
|
|
36125
|
+
* by a wake — and 38% of the tracks that promotion then re-created had never
|
|
36126
|
+
* moved in their entire life. One bad box per parked object was enough.
|
|
36070
36127
|
*
|
|
36071
36128
|
* A detection can suppress at most one spawn even if it overlaps two entries
|
|
36072
36129
|
* (`suppressedIndices` is a set).
|
|
36073
36130
|
*/
|
|
36074
36131
|
function partitionDetectionsAgainstRegistry(input) {
|
|
36075
36132
|
const { entries, detections, config } = input;
|
|
36133
|
+
const armed = input.armedEntryIds ?? EMPTY_ARMED;
|
|
36076
36134
|
const suppressed = /* @__PURE__ */ new Set();
|
|
36077
36135
|
const confirmed = [];
|
|
36078
36136
|
const woken = [];
|
|
36137
|
+
const toArm = [];
|
|
36138
|
+
const toDisarm = [];
|
|
36079
36139
|
for (const entry of entries) {
|
|
36080
36140
|
let bestIou = 0;
|
|
36081
36141
|
let bestIdx = -1;
|
|
@@ -36088,23 +36148,37 @@ function partitionDetectionsAgainstRegistry(input) {
|
|
|
36088
36148
|
bestIdx = di;
|
|
36089
36149
|
}
|
|
36090
36150
|
}
|
|
36091
|
-
if (bestIdx < 0 || bestIou < config.wakeAssocIou)
|
|
36151
|
+
if (bestIdx < 0 || bestIou < config.wakeAssocIou) {
|
|
36152
|
+
if (armed.has(entry.id)) woken.push(entry.id);
|
|
36153
|
+
continue;
|
|
36154
|
+
}
|
|
36092
36155
|
const detCentroid = centroid(detections[bestIdx].bbox);
|
|
36093
|
-
|
|
36156
|
+
const strong = bestIou >= config.suppressIou;
|
|
36157
|
+
if (strong || centroidInside(entry.bbox, detCentroid)) {
|
|
36094
36158
|
suppressed.add(bestIdx);
|
|
36095
36159
|
confirmed.push({
|
|
36096
36160
|
entryId: entry.id,
|
|
36097
36161
|
className: entry.className,
|
|
36098
|
-
bbox: { ...entry.bbox }
|
|
36162
|
+
bbox: { ...entry.bbox },
|
|
36163
|
+
observedBbox: { ...detections[bestIdx].bbox },
|
|
36164
|
+
strong
|
|
36099
36165
|
});
|
|
36100
|
-
|
|
36166
|
+
if (armed.has(entry.id)) toDisarm.push(entry.id);
|
|
36167
|
+
} else if (armed.has(entry.id)) woken.push(entry.id);
|
|
36168
|
+
else {
|
|
36169
|
+
toArm.push(entry.id);
|
|
36170
|
+
suppressed.add(bestIdx);
|
|
36171
|
+
}
|
|
36101
36172
|
}
|
|
36102
36173
|
return {
|
|
36103
36174
|
suppressedIndices: suppressed,
|
|
36104
36175
|
confirmed,
|
|
36105
|
-
wokenEntryIds: woken
|
|
36176
|
+
wokenEntryIds: woken,
|
|
36177
|
+
armedEntryIds: toArm,
|
|
36178
|
+
disarmedEntryIds: toDisarm
|
|
36106
36179
|
};
|
|
36107
36180
|
}
|
|
36181
|
+
var EMPTY_ARMED = /* @__PURE__ */ new Set();
|
|
36108
36182
|
/**
|
|
36109
36183
|
* A track is promoted when, over the recent `windowMs`, its centroid barely
|
|
36110
36184
|
* moved (both net displacement and path span below the static thresholds) AND
|
|
@@ -36677,6 +36751,21 @@ var StationaryObjectRegistry = class {
|
|
|
36677
36751
|
* between motion sessions; that silence is not evidence the object left,
|
|
36678
36752
|
* so quiet minutes must not age the entries (see {@link sweep}). */
|
|
36679
36753
|
lastFrameAtByDevice = /* @__PURE__ */ new Map();
|
|
36754
|
+
/**
|
|
36755
|
+
* Entries carrying ONE unconfirmed departure read, per device (D49, D174).
|
|
36756
|
+
*
|
|
36757
|
+
* Retiring an entry destroys work — the object's registry identity and the
|
|
36758
|
+
* suppression that keeps it from re-spawning a track every promotion window.
|
|
36759
|
+
* A single frame whose box slid off the parked spot is a fallible read (a
|
|
36760
|
+
* partial occlusion, a shadow, one bad box), so it only ARMS the entry here.
|
|
36761
|
+
* The retirement happens on the next frame that AGREES; a confirming frame
|
|
36762
|
+
* clears the strike.
|
|
36763
|
+
*/
|
|
36764
|
+
departureStrikes = /* @__PURE__ */ new Map();
|
|
36765
|
+
/** Detections absorbed by an existing entry since the last sweep, per device.
|
|
36766
|
+
* Rolled up into ONE log line per sweep — this fires on every frame of every
|
|
36767
|
+
* parked object, so it can never be a line of its own. */
|
|
36768
|
+
absorbedSinceSweep = /* @__PURE__ */ new Map();
|
|
36680
36769
|
constructor(deps) {
|
|
36681
36770
|
this.logger = deps.logger;
|
|
36682
36771
|
this.ledger = new DurableLedger({
|
|
@@ -36749,12 +36838,15 @@ var StationaryObjectRegistry = class {
|
|
|
36749
36838
|
if (entries.length === 0) return {
|
|
36750
36839
|
suppressedIndices: /* @__PURE__ */ new Set(),
|
|
36751
36840
|
confirmed: [],
|
|
36752
|
-
wokenEntryIds: []
|
|
36841
|
+
wokenEntryIds: [],
|
|
36842
|
+
armedEntryIds: [],
|
|
36843
|
+
disarmedEntryIds: []
|
|
36753
36844
|
};
|
|
36754
36845
|
return partitionDetectionsAgainstRegistry({
|
|
36755
36846
|
entries,
|
|
36756
36847
|
detections: input.detections,
|
|
36757
|
-
config: config ?? this.matchConfig
|
|
36848
|
+
config: config ?? this.matchConfig,
|
|
36849
|
+
...this.departureStrikes.has(input.deviceId) ? { armedEntryIds: this.departureStrikes.get(input.deviceId) } : {}
|
|
36758
36850
|
});
|
|
36759
36851
|
}
|
|
36760
36852
|
/** Fold a frame's gate result back into state: advance confirmed entries'
|
|
@@ -36771,15 +36863,41 @@ var StationaryObjectRegistry = class {
|
|
|
36771
36863
|
if (!e) continue;
|
|
36772
36864
|
this.ledger.stage({
|
|
36773
36865
|
...e,
|
|
36774
|
-
lastConfirmedAt: input.timestamp
|
|
36866
|
+
lastConfirmedAt: input.timestamp,
|
|
36867
|
+
...c.strong ? { bbox: { ...c.observedBbox } } : {}
|
|
36775
36868
|
});
|
|
36776
36869
|
this.dirty.add(c.entryId);
|
|
36777
36870
|
}
|
|
36871
|
+
this.absorbedSinceSweep.set(input.deviceId, (this.absorbedSinceSweep.get(input.deviceId) ?? 0) + input.confirmed.length);
|
|
36872
|
+
if (input.armedEntryIds && input.armedEntryIds.length > 0) {
|
|
36873
|
+
let armed = this.departureStrikes.get(input.deviceId);
|
|
36874
|
+
if (!armed) {
|
|
36875
|
+
armed = /* @__PURE__ */ new Set();
|
|
36876
|
+
this.departureStrikes.set(input.deviceId, armed);
|
|
36877
|
+
}
|
|
36878
|
+
for (const id of input.armedEntryIds) if (ids.has(id)) armed.add(id);
|
|
36879
|
+
}
|
|
36880
|
+
const armed = this.departureStrikes.get(input.deviceId);
|
|
36881
|
+
if (armed && input.disarmedEntryIds) {
|
|
36882
|
+
for (const id of input.disarmedEntryIds) armed.delete(id);
|
|
36883
|
+
if (armed.size === 0) this.departureStrikes.delete(input.deviceId);
|
|
36884
|
+
}
|
|
36778
36885
|
for (const id of input.wokenEntryIds) {
|
|
36779
36886
|
if (!ids.has(id)) continue;
|
|
36780
36887
|
const e = this.ledger.get(id);
|
|
36781
36888
|
if (!e) continue;
|
|
36782
36889
|
this.dropEntry(input.deviceId, id);
|
|
36890
|
+
this.logger.info("stationary entry retired", {
|
|
36891
|
+
tags: { deviceId: input.deviceId },
|
|
36892
|
+
meta: {
|
|
36893
|
+
deviceId: input.deviceId,
|
|
36894
|
+
entryId: id,
|
|
36895
|
+
className: e.className,
|
|
36896
|
+
reason: "departed",
|
|
36897
|
+
parkedForMs: input.timestamp - e.becameStationaryAt,
|
|
36898
|
+
...e.sourceTrackId !== void 0 ? { sourceTrackId: e.sourceTrackId } : {}
|
|
36899
|
+
}
|
|
36900
|
+
});
|
|
36783
36901
|
this.onChange?.({
|
|
36784
36902
|
phase: "departed",
|
|
36785
36903
|
entry: e,
|
|
@@ -36826,6 +36944,18 @@ var StationaryObjectRegistry = class {
|
|
|
36826
36944
|
if (observedAt - e.lastConfirmedAt > ttl) {
|
|
36827
36945
|
this.dropEntry(deviceId, id);
|
|
36828
36946
|
retired.push(e);
|
|
36947
|
+
this.logger.info("stationary entry retired", {
|
|
36948
|
+
tags: { deviceId },
|
|
36949
|
+
meta: {
|
|
36950
|
+
deviceId,
|
|
36951
|
+
entryId: id,
|
|
36952
|
+
className: e.className,
|
|
36953
|
+
reason: "ttl",
|
|
36954
|
+
ttlMs: ttl,
|
|
36955
|
+
unconfirmedForMs: observedAt - e.lastConfirmedAt,
|
|
36956
|
+
parkedForMs: observedAt - e.becameStationaryAt
|
|
36957
|
+
}
|
|
36958
|
+
});
|
|
36829
36959
|
this.onChange?.({
|
|
36830
36960
|
phase: "departed",
|
|
36831
36961
|
entry: e,
|
|
@@ -36835,6 +36965,18 @@ var StationaryObjectRegistry = class {
|
|
|
36835
36965
|
}
|
|
36836
36966
|
if (ids.size === 0) this.idsByDevice.delete(deviceId);
|
|
36837
36967
|
}
|
|
36968
|
+
for (const [deviceId, absorbed] of this.absorbedSinceSweep) {
|
|
36969
|
+
if (absorbed === 0) continue;
|
|
36970
|
+
this.logger.info("stationary registry absorbed detections", {
|
|
36971
|
+
tags: { deviceId },
|
|
36972
|
+
meta: {
|
|
36973
|
+
deviceId,
|
|
36974
|
+
absorbed,
|
|
36975
|
+
entries: this.count(deviceId)
|
|
36976
|
+
}
|
|
36977
|
+
});
|
|
36978
|
+
}
|
|
36979
|
+
this.absorbedSinceSweep.clear();
|
|
36838
36980
|
for (const id of [...this.dirty]) {
|
|
36839
36981
|
this.dirty.delete(id);
|
|
36840
36982
|
const entry = this.ledger.get(id);
|
|
@@ -36847,6 +36989,8 @@ var StationaryObjectRegistry = class {
|
|
|
36847
36989
|
* Used on device unbind; a rebind reloads from the store. */
|
|
36848
36990
|
forgetDevice(deviceId) {
|
|
36849
36991
|
this.lastFrameAtByDevice.delete(deviceId);
|
|
36992
|
+
this.departureStrikes.delete(deviceId);
|
|
36993
|
+
this.absorbedSinceSweep.delete(deviceId);
|
|
36850
36994
|
const ids = this.idsByDevice.get(deviceId);
|
|
36851
36995
|
if (!ids) return;
|
|
36852
36996
|
for (const id of ids) {
|
|
@@ -36858,6 +37002,8 @@ var StationaryObjectRegistry = class {
|
|
|
36858
37002
|
/** Delete every persisted + in-memory entry for a device (operator wipe). */
|
|
36859
37003
|
async clearDevice(deviceId) {
|
|
36860
37004
|
this.lastFrameAtByDevice.delete(deviceId);
|
|
37005
|
+
this.departureStrikes.delete(deviceId);
|
|
37006
|
+
this.absorbedSinceSweep.delete(deviceId);
|
|
36861
37007
|
const ids = this.idsByDevice.get(deviceId);
|
|
36862
37008
|
if (ids) {
|
|
36863
37009
|
for (const id of [...ids]) {
|
|
@@ -36875,10 +37021,17 @@ var StationaryObjectRegistry = class {
|
|
|
36875
37021
|
}
|
|
36876
37022
|
set.add(id);
|
|
36877
37023
|
}
|
|
36878
|
-
/** Retire one entry: index, staged advance and durable row
|
|
37024
|
+
/** Retire one entry: index, staged advance, pending strike and durable row
|
|
37025
|
+
* all go. A strike that outlived its entry would arm the NEXT entry to reuse
|
|
37026
|
+
* the id — the registry mints UUIDs, but the invariant is cheap to keep. */
|
|
36879
37027
|
dropEntry(deviceId, id) {
|
|
36880
37028
|
this.idsByDevice.get(deviceId)?.delete(id);
|
|
36881
37029
|
this.dirty.delete(id);
|
|
37030
|
+
const armed = this.departureStrikes.get(deviceId);
|
|
37031
|
+
if (armed) {
|
|
37032
|
+
armed.delete(id);
|
|
37033
|
+
if (armed.size === 0) this.departureStrikes.delete(deviceId);
|
|
37034
|
+
}
|
|
36882
37035
|
this.ledger.forget(id);
|
|
36883
37036
|
}
|
|
36884
37037
|
};
|
|
@@ -40922,6 +41075,26 @@ function isAfterCursor(row, cursor) {
|
|
|
40922
41075
|
}
|
|
40923
41076
|
//#endregion
|
|
40924
41077
|
//#region src/pipeline-analytics/store/track-store.ts
|
|
41078
|
+
/**
|
|
41079
|
+
* WHY a track's row was written EARLY — i.e. by {@link TrackStore.closeActive}
|
|
41080
|
+
* rather than by the ordinary TTL expiry.
|
|
41081
|
+
*
|
|
41082
|
+
* `'stationary-promotion'` is the only member and means: the tracker decided
|
|
41083
|
+
* this subject had PARKED, handed it to the stationary registry, and the row
|
|
41084
|
+
* was written so its media, events and vector keep an owner (D161). It is not a
|
|
41085
|
+
* quality signal and it never hides the track — a parked car is still a track,
|
|
41086
|
+
* still on the events page, still swept by the same retention. It exists so a
|
|
41087
|
+
* consumer that wants PASSAGES can say so exactly, instead of re-deriving
|
|
41088
|
+
* "did it move" from a trajectory downstream and getting it wrong on the
|
|
41089
|
+
* subject that stood still for one honest reason.
|
|
41090
|
+
*
|
|
41091
|
+
* An ABSENT value means "expired normally, or written before this marker
|
|
41092
|
+
* existed". Never read an absence as a positive — same rule as `hasFace`.
|
|
41093
|
+
*/
|
|
41094
|
+
var TRACK_CLOSE_REASONS = ["stationary-promotion"];
|
|
41095
|
+
function parseCloseReason(value) {
|
|
41096
|
+
return TRACK_CLOSE_REASONS.find((reason) => reason === value);
|
|
41097
|
+
}
|
|
40925
41098
|
var DEFAULT_CONFIG = {
|
|
40926
41099
|
ttlMs: 3e4,
|
|
40927
41100
|
maxPositionHistory: 300
|
|
@@ -41144,6 +41317,10 @@ var TRACKS_COLUMNS = [
|
|
|
41144
41317
|
{
|
|
41145
41318
|
name: "frameHeight",
|
|
41146
41319
|
type: "INTEGER"
|
|
41320
|
+
},
|
|
41321
|
+
{
|
|
41322
|
+
name: "closedReason",
|
|
41323
|
+
type: "TEXT"
|
|
41147
41324
|
}
|
|
41148
41325
|
];
|
|
41149
41326
|
var TRACKS_INDEXES = [{
|
|
@@ -41203,7 +41380,8 @@ function cloneTrack(t) {
|
|
|
41203
41380
|
} : {},
|
|
41204
41381
|
...t.debug !== void 0 ? { debug: t.debug } : {},
|
|
41205
41382
|
...t.hasFace !== void 0 ? { hasFace: t.hasFace } : {},
|
|
41206
|
-
...t.hasRider !== void 0 ? { hasRider: t.hasRider } : {}
|
|
41383
|
+
...t.hasRider !== void 0 ? { hasRider: t.hasRider } : {},
|
|
41384
|
+
...t.closedReason !== void 0 ? { closedReason: t.closedReason } : {}
|
|
41207
41385
|
};
|
|
41208
41386
|
}
|
|
41209
41387
|
var TrackStore = class {
|
|
@@ -41772,13 +41950,22 @@ var TrackStore = class {
|
|
|
41772
41950
|
* nothing has been written under that id yet. It is never correct for a track
|
|
41773
41951
|
* that ran.
|
|
41774
41952
|
*
|
|
41953
|
+
* **The row SAYS why it was closed early** (`reason` → the `closedReason`
|
|
41954
|
+
* column). Persisting a promoted track put its footprint back under the
|
|
41955
|
+
* retention sweep, and it also put the parked car back in front of every
|
|
41956
|
+
* consumer that reads the window's tracks: the operator's nightly digest went
|
|
41957
|
+
* from 1-2 tiles to 10, most of them the same stationary car. The marker is
|
|
41958
|
+
* written HERE, at the one place that knows, so a consumer can ask for
|
|
41959
|
+
* passages exactly instead of guessing at movement from a stored trajectory.
|
|
41960
|
+
*
|
|
41775
41961
|
* Returns whether an active entry was found. THROWS if the persist fails: the
|
|
41776
41962
|
* caller must log it, because a swallowed failure here IS the leak.
|
|
41777
41963
|
*/
|
|
41778
|
-
async closeActive(trackId) {
|
|
41964
|
+
async closeActive(trackId, reason) {
|
|
41779
41965
|
const t = this.active.get(trackId);
|
|
41780
41966
|
if (t === void 0) return false;
|
|
41781
41967
|
t.active = false;
|
|
41968
|
+
t.closedReason = reason;
|
|
41782
41969
|
const record = cloneTrack(t);
|
|
41783
41970
|
await this.persistCompleted(record);
|
|
41784
41971
|
this.active.delete(trackId);
|
|
@@ -42360,6 +42547,7 @@ var TrackStore = class {
|
|
|
42360
42547
|
...t.debug !== void 0 ? { debug: t.debug } : {},
|
|
42361
42548
|
...t.hasFace !== void 0 ? { hasFace: t.hasFace } : {},
|
|
42362
42549
|
...t.hasRider !== void 0 ? { hasRider: t.hasRider } : {},
|
|
42550
|
+
...t.closedReason !== void 0 ? { closedReason: t.closedReason } : {},
|
|
42363
42551
|
...envelope !== null && dims !== void 0 ? {
|
|
42364
42552
|
envMinX: envelope.minX,
|
|
42365
42553
|
envMinY: envelope.minY,
|
|
@@ -42397,6 +42585,7 @@ var TrackStore = class {
|
|
|
42397
42585
|
const hasFace = data["hasFace"];
|
|
42398
42586
|
/** Same read as `hasFace` — NULL / missing stays ABSENT, never `false`. */
|
|
42399
42587
|
const hasRider = data["hasRider"];
|
|
42588
|
+
const closedReason = parseCloseReason(data["closedReason"]);
|
|
42400
42589
|
const envMinX = data["envMinX"];
|
|
42401
42590
|
const envMinY = data["envMinY"];
|
|
42402
42591
|
const envMaxX = data["envMaxX"];
|
|
@@ -42434,6 +42623,7 @@ var TrackStore = class {
|
|
|
42434
42623
|
...typeof debug === "boolean" ? { debug } : {},
|
|
42435
42624
|
...typeof hasFace === "boolean" ? { hasFace } : {},
|
|
42436
42625
|
...typeof hasRider === "boolean" ? { hasRider } : {},
|
|
42626
|
+
...closedReason !== void 0 ? { closedReason } : {},
|
|
42437
42627
|
...envelope !== null ? { envelope } : {}
|
|
42438
42628
|
};
|
|
42439
42629
|
}
|
|
@@ -49709,11 +49899,22 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
49709
49899
|
async onInitialize() {
|
|
49710
49900
|
const api = this.ctx.api;
|
|
49711
49901
|
if (!api) throw new Error("pipeline-analytics requires ctx.api (device-manager + settings-store)");
|
|
49712
|
-
|
|
49713
|
-
|
|
49902
|
+
const bootT0 = Date.now();
|
|
49903
|
+
const step = async (name, fn) => {
|
|
49904
|
+
const t0 = Date.now();
|
|
49905
|
+
const out = await fn();
|
|
49906
|
+
this.ctx.logger.info("pipeline-analytics boot step", { meta: {
|
|
49907
|
+
step: name,
|
|
49908
|
+
ms: Date.now() - t0,
|
|
49909
|
+
sinceBootMs: Date.now() - bootT0
|
|
49910
|
+
} });
|
|
49911
|
+
return out;
|
|
49912
|
+
};
|
|
49913
|
+
await step("declareCollections", () => this.declareCollections(api));
|
|
49914
|
+
await step("declareAlarmPanel", () => this.declareAlarmPanel(api));
|
|
49714
49915
|
const logger = this.ctx.logger;
|
|
49715
|
-
const storage = await this.resolveMediaStorage(logger);
|
|
49716
|
-
const stores = await this.buildStores(api, logger, storage);
|
|
49916
|
+
const storage = await step("resolveMediaStorage", () => this.resolveMediaStorage(logger));
|
|
49917
|
+
const stores = await step("buildStores", () => this.buildStores(api, logger, storage));
|
|
49717
49918
|
const rawNodeId = this.ctx.kernel.localNodeId ?? this.ctx.id;
|
|
49718
49919
|
const ownNodeId = rawNodeId.includes("/") ? rawNodeId.split("/")[0] : rawNodeId;
|
|
49719
49920
|
this.eventsOpsLog = new OpsLogStore({
|
|
@@ -49722,7 +49923,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
49722
49923
|
nodeId: ownNodeId
|
|
49723
49924
|
});
|
|
49724
49925
|
{
|
|
49725
|
-
const designated = await this.postProcessingNodeState.get();
|
|
49926
|
+
const designated = await step("postProcessingNodeState", () => this.postProcessingNodeState.get());
|
|
49726
49927
|
this.isPostProcessingNode = ownNodeId === designated;
|
|
49727
49928
|
if (!this.isPostProcessingNode) logger.info("pipeline-analytics INERT on this node — post-processing assigned elsewhere", { meta: {
|
|
49728
49929
|
ownNodeId,
|
|
@@ -49746,8 +49947,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
49746
49947
|
this.buildRecognizers(api, logger, stores, transport);
|
|
49747
49948
|
this.buildDetailDispatcher(api, logger, transport);
|
|
49748
49949
|
const capProviders = this.buildAnalyticsProviders(api, logger);
|
|
49749
|
-
await this.buildNotificationCenter(api, logger, stores);
|
|
49750
|
-
await this.serveEventMediaDataPlane();
|
|
49950
|
+
await step("buildNotificationCenter", () => this.buildNotificationCenter(api, logger, stores));
|
|
49951
|
+
await step("serveEventMediaDataPlane", () => this.serveEventMediaDataPlane());
|
|
49751
49952
|
this.subscriptionUnsubs = wireSubscriptions({
|
|
49752
49953
|
eventBus: this.ctx.eventBus,
|
|
49753
49954
|
isPostProcessingNode: this.isPostProcessingNode,
|
|
@@ -49763,11 +49964,11 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
49763
49964
|
onClusterTopology: (nodes) => this.nodeLiveness.applySnapshot(nodes, Date.now()),
|
|
49764
49965
|
onSystemEvent: (event) => this.notificationCenter?.onSystemEvent(event)
|
|
49765
49966
|
});
|
|
49766
|
-
if (this.isPostProcessingNode) await this.startEmbeddingDispatcher(logger, transport);
|
|
49767
|
-
await this.buildSceneMonitor(api, logger);
|
|
49967
|
+
if (this.isPostProcessingNode) await step("startEmbeddingDispatcher", () => this.startEmbeddingDispatcher(logger, transport));
|
|
49968
|
+
await step("buildSceneMonitor", () => this.buildSceneMonitor(api, logger));
|
|
49768
49969
|
this.startSweepTimers();
|
|
49769
49970
|
this.ctx.logger.info("pipeline-analytics subscribers installed");
|
|
49770
|
-
const providers = this.buildProviderRegistrations(api, stores, capProviders);
|
|
49971
|
+
const providers = await step("buildProviderRegistrations", () => this.buildProviderRegistrations(api, stores, capProviders));
|
|
49771
49972
|
const isHub = ownNodeId === "hub";
|
|
49772
49973
|
const ncHandlers = isHub ? this.buildNcActionHandlers(api) : void 0;
|
|
49773
49974
|
return {
|
|
@@ -49942,26 +50143,30 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
49942
50143
|
}
|
|
49943
50144
|
async declareCollections(api) {
|
|
49944
50145
|
this.settingsStore = api.settingsStore;
|
|
49945
|
-
await
|
|
49946
|
-
|
|
49947
|
-
|
|
49948
|
-
|
|
49949
|
-
|
|
49950
|
-
|
|
49951
|
-
|
|
49952
|
-
|
|
49953
|
-
|
|
49954
|
-
|
|
49955
|
-
|
|
49956
|
-
|
|
49957
|
-
|
|
49958
|
-
|
|
49959
|
-
|
|
49960
|
-
|
|
50146
|
+
await Promise.all([
|
|
50147
|
+
TrackStore.declare(api.settingsStore),
|
|
50148
|
+
MediaStore.declare(api.settingsStore),
|
|
50149
|
+
EventStore.declare(api.settingsStore),
|
|
50150
|
+
SensorEventStore.declare(api.settingsStore),
|
|
50151
|
+
IdentityStore.declare(api.settingsStore),
|
|
50152
|
+
FaceStore.declare(api.settingsStore),
|
|
50153
|
+
PlateStore.declare(api.settingsStore),
|
|
50154
|
+
VehicleStore.declare(api.settingsStore),
|
|
50155
|
+
StationaryObjectRegistry.declare(api.settingsStore),
|
|
50156
|
+
RetrainFrameStore.declare(api.settingsStore),
|
|
50157
|
+
RetrainAnnotationStore.declare(api.settingsStore),
|
|
50158
|
+
OpsLogStore.declare(api.settingsStore),
|
|
50159
|
+
AnalyticsLts.declare(api.settingsStore),
|
|
50160
|
+
SceneStore.declare(api.settingsStore),
|
|
50161
|
+
NotificationCenter.declare(api.settingsStore)
|
|
50162
|
+
]);
|
|
50163
|
+
runLabelTierMigration({
|
|
49961
50164
|
store: api.settingsStore,
|
|
49962
50165
|
logger: this.ctx.logger,
|
|
49963
50166
|
now: () => Date.now()
|
|
49964
|
-
}, [TRACKS_COLLECTION, OBJECT_EVENTS_COLLECTION])
|
|
50167
|
+
}, [TRACKS_COLLECTION, OBJECT_EVENTS_COLLECTION]).catch((err) => {
|
|
50168
|
+
this.ctx.logger.warn("label tier migration failed — will retry next boot", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
50169
|
+
});
|
|
49965
50170
|
}
|
|
49966
50171
|
/** Event-media storage. By default it uses `ctx.kernel.storage`, rooted at
|
|
49967
50172
|
* the node data dir (appData) → heavy thumbnail/track-frame churn hammers
|
|
@@ -50621,7 +50826,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
50621
50826
|
lastSeen: t.lastSeen,
|
|
50622
50827
|
...t.importance !== void 0 ? { importance: t.importance } : {},
|
|
50623
50828
|
zones: [...t.zonesVisited],
|
|
50624
|
-
...t.classes !== void 0 ? { classes: [...t.classes] } : {}
|
|
50829
|
+
...t.classes !== void 0 ? { classes: [...t.classes] } : {},
|
|
50830
|
+
...t.closedReason === "stationary-promotion" ? { stationary: true } : {}
|
|
50625
50831
|
}));
|
|
50626
50832
|
},
|
|
50627
50833
|
listMediaInfo: async (trackId) => {
|
|
@@ -51725,10 +51931,12 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
51725
51931
|
this.trackStore?.markRiderFolded(fold.trackId);
|
|
51726
51932
|
}
|
|
51727
51933
|
if (source === "pipeline") this.stationaryRegistry?.noteFrame(deviceId, result.timestamp);
|
|
51728
|
-
if (this.stationaryRegistry && (result.stationaryConfirmed.length > 0 || result.stationaryWoken.length > 0)) this.stationaryRegistry.applyFrameOutcome({
|
|
51934
|
+
if (this.stationaryRegistry && (result.stationaryConfirmed.length > 0 || result.stationaryWoken.length > 0 || result.stationaryArmed.length > 0 || result.stationaryDisarmed.length > 0)) this.stationaryRegistry.applyFrameOutcome({
|
|
51729
51935
|
deviceId,
|
|
51730
51936
|
confirmed: result.stationaryConfirmed,
|
|
51731
51937
|
wokenEntryIds: result.stationaryWoken,
|
|
51938
|
+
armedEntryIds: result.stationaryArmed,
|
|
51939
|
+
disarmedEntryIds: result.stationaryDisarmed,
|
|
51732
51940
|
timestamp: result.timestamp
|
|
51733
51941
|
});
|
|
51734
51942
|
const stationaryViews = this.stationaryRegistry?.listViews(deviceId) ?? [];
|
|
@@ -53785,7 +53993,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
53785
53993
|
};
|
|
53786
53994
|
if (plausible) this.stationaryRegistry?.promote(entry);
|
|
53787
53995
|
processor.dropTrack(track.trackId);
|
|
53788
|
-
this.trackStore?.closeActive(track.trackId).catch((err) => {
|
|
53996
|
+
this.trackStore?.closeActive(track.trackId, "stationary-promotion").catch((err) => {
|
|
53789
53997
|
this.ctx.logger.warn("stationary promotion: closing the source track failed — its media, events and vector are now unowned", {
|
|
53790
53998
|
tags: { deviceId },
|
|
53791
53999
|
meta: {
|
|
@@ -53888,7 +54096,9 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
53888
54096
|
if (!s.enabled) return {
|
|
53889
54097
|
suppressedIndices: /* @__PURE__ */ new Set(),
|
|
53890
54098
|
confirmed: [],
|
|
53891
|
-
wokenEntryIds: []
|
|
54099
|
+
wokenEntryIds: [],
|
|
54100
|
+
armedEntryIds: [],
|
|
54101
|
+
disarmedEntryIds: []
|
|
53892
54102
|
};
|
|
53893
54103
|
const matchConfig = {
|
|
53894
54104
|
suppressIou: s.suppressIou,
|