@camstack/addon-post-analysis 1.2.654 → 1.2.655
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/CHANGELOG.md +8 -0
- package/dist/{clip-model-registry-CFQBxtE2.js → clip-model-registry-C-en5vSD.js} +68244 -67588
- package/dist/{clip-model-registry-BX6j2lnF.mjs → clip-model-registry-_b3KHYFm.mjs} +68232 -67612
- 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-Vag6rhEh.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CF-Mts9j.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C7OJ7wek.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-CnvnZsmk.mjs → hostInit-BSfz5s98.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +315 -72
- package/dist/pipeline-analytics/index.mjs +314 -71
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BUwWKfHC.mjs +0 -26
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_clip_model_registry = require("../clip-model-registry-
|
|
5
|
+
const require_clip_model_registry = require("../clip-model-registry-C-en5vSD.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_clip_model_registry.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -895,7 +895,9 @@ function describeGrant(grant) {
|
|
|
895
895
|
return grant.action === "disarm" ? "Alarm disarmed" : `Alarm armed${grant.mode !== void 0 ? ` (${grant.mode})` : ""}`;
|
|
896
896
|
case "addon-update": return `${grant.packages.length} addon update(s) on ${grant.nodeId}`;
|
|
897
897
|
case "server-update": return `Server update on ${grant.nodeId}`;
|
|
898
|
-
case "detection-verdict":
|
|
898
|
+
case "detection-verdict":
|
|
899
|
+
if (grant.verdict === "wrong-label") return "Marked label wrong, class right — kept for retraining";
|
|
900
|
+
return grant.verdict === "correct" ? "Marked correct — kept for retraining" : grant.suggestedClass === void 0 ? "Marked wrong — kept for retraining" : `Marked wrong, it was a ${grant.suggestedClass} — kept for retraining`;
|
|
899
901
|
}
|
|
900
902
|
}
|
|
901
903
|
/** The audit line's `meta`, per family. A snooze that logged a `sequence` it
|
|
@@ -969,7 +971,7 @@ var FAILURE_COPY = {
|
|
|
969
971
|
/** The question a link-only channel lands on, answered by a POST form. */
|
|
970
972
|
function confirmPage(url, grant) {
|
|
971
973
|
const action = `${url.pathname}?exp=${encodeURIComponent(url.searchParams.get("exp") ?? "")}&sig=${encodeURIComponent(url.searchParams.get("sig") ?? "")}&confirm=1`;
|
|
972
|
-
return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Confirm</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#111;color:#eee}main{max-width:24rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 1rem}button{font:inherit;padding:.6rem 1.4rem;border-radius:.5rem;border:0;background:#3b82f6;color:#fff}</style><main><h1>Confirm</h1><p>${grant.verdict === "correct" ? "Mark this detection as correct?" : grant.suggestedClass === void 0 ? "Mark this detection as wrong?" : `Mark this detection as wrong, it was a ${grant.suggestedClass}?`}</p><form method="post" action="${action}"><button type="submit">Confirm</button></form></main>`;
|
|
974
|
+
return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Confirm</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#111;color:#eee}main{max-width:24rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 1rem}button{font:inherit;padding:.6rem 1.4rem;border-radius:.5rem;border:0;background:#3b82f6;color:#fff}</style><main><h1>Confirm</h1><p>${grant.verdict === "wrong-label" ? "Mark the label of this detection as wrong (the class is right)?" : grant.verdict === "correct" ? "Mark this detection as correct?" : grant.suggestedClass === void 0 ? "Mark this detection as wrong?" : `Mark this detection as wrong, it was a ${grant.suggestedClass}?`}</p><form method="post" action="${action}"><button type="submit">Confirm</button></form></main>`;
|
|
973
975
|
}
|
|
974
976
|
function page(title, detail) {
|
|
975
977
|
return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${title}</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#111;color:#eee}main{max-width:24rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 .5rem}p{margin:0;color:#aaa}</style><main><h1>${title}</h1><p>${detail}</p></main>`;
|
|
@@ -2953,7 +2955,6 @@ var NIGHT = "night";
|
|
|
2953
2955
|
var IR = "ir";
|
|
2954
2956
|
var DAWN = "dawn";
|
|
2955
2957
|
var DUSK = "dusk";
|
|
2956
|
-
var DEG = Math.PI / 180;
|
|
2957
2958
|
/**
|
|
2958
2959
|
* Mean channel spread below which a frame is judged infrared.
|
|
2959
2960
|
*
|
|
@@ -2975,38 +2976,6 @@ var IR_CHROMA_THRESHOLD = 6;
|
|
|
2975
2976
|
* scene reports `unknown` — the honest answer.
|
|
2976
2977
|
*/
|
|
2977
2978
|
var TWILIGHT_BAND_MS = 2400 * 1e3;
|
|
2978
|
-
/** Julian day number for a JS Date (UTC). */
|
|
2979
|
-
function toJulian(date) {
|
|
2980
|
-
return date.getTime() / 864e5 + 2440587.5;
|
|
2981
|
-
}
|
|
2982
|
-
/** Julian day → epoch ms (UTC). */
|
|
2983
|
-
function fromJulian(j) {
|
|
2984
|
-
return (j - 2440587.5) * 864e5;
|
|
2985
|
-
}
|
|
2986
|
-
/**
|
|
2987
|
-
* Sunrise/sunset (UTC epoch ms) for a location and date via the standard NOAA
|
|
2988
|
-
* sunrise equation. Returns `null` for the polar edge cases (sun never
|
|
2989
|
-
* rises/sets that day) so the caller can degrade to the clock split.
|
|
2990
|
-
*/
|
|
2991
|
-
function sunTimes(lat, lng, date) {
|
|
2992
|
-
const jDate = toJulian(date);
|
|
2993
|
-
const jStar = Math.round(jDate - 2451545 + 8e-4) - lng / 360;
|
|
2994
|
-
const m = (357.5291 + .98560028 * jStar) % 360;
|
|
2995
|
-
const mRad = m * DEG;
|
|
2996
|
-
const lambda = (m + (1.9148 * Math.sin(mRad) + .02 * Math.sin(2 * mRad) + 3e-4 * Math.sin(3 * mRad)) + 180 + 102.9372) % 360 * DEG;
|
|
2997
|
-
const jTransit = 2451545 + jStar + .0053 * Math.sin(mRad) - .0069 * Math.sin(2 * lambda);
|
|
2998
|
-
const declination = Math.asin(Math.sin(lambda) * Math.sin(23.44 * DEG));
|
|
2999
|
-
const latRad = lat * DEG;
|
|
3000
|
-
const cosOmega = (Math.sin(-.833 * DEG) - Math.sin(latRad) * Math.sin(declination)) / (Math.cos(latRad) * Math.cos(declination));
|
|
3001
|
-
if (cosOmega > 1 || cosOmega < -1) return null;
|
|
3002
|
-
const omega = Math.acos(cosOmega) / DEG;
|
|
3003
|
-
const jRise = jTransit - omega / 360;
|
|
3004
|
-
const jSet = jTransit + omega / 360;
|
|
3005
|
-
return {
|
|
3006
|
-
rise: fromJulian(jRise),
|
|
3007
|
-
set: fromJulian(jSet)
|
|
3008
|
-
};
|
|
3009
|
-
}
|
|
3010
2979
|
/** Coarse UTC clock split — day between 06:00 and 20:00 UTC. Deterministic
|
|
3011
2980
|
* last resort when no coordinates let us compute real sun-times. */
|
|
3012
2981
|
function clockCondition(now) {
|
|
@@ -3047,7 +3016,7 @@ function resolveConditionDetailed(input) {
|
|
|
3047
3016
|
source: "ir-chroma"
|
|
3048
3017
|
};
|
|
3049
3018
|
if (input.coordinates) {
|
|
3050
|
-
const times = sunTimes(input.coordinates.lat, input.coordinates.lng, input.now);
|
|
3019
|
+
const times = require_clip_model_registry.sunTimes(input.coordinates.lat, input.coordinates.lng, input.now);
|
|
3051
3020
|
if (times) {
|
|
3052
3021
|
const t = input.now.getTime();
|
|
3053
3022
|
if (Math.abs(t - times.rise) <= TWILIGHT_BAND_MS) return {
|
|
@@ -9289,6 +9258,7 @@ var en_default = {
|
|
|
9289
9258
|
"button.verdict.correct": "Correct",
|
|
9290
9259
|
"button.verdict.wrong": "Wrong",
|
|
9291
9260
|
"button.verdict.wrongAs": "Wrong: it is {{class}}",
|
|
9261
|
+
"button.verdict.wrongLabel": "Wrong label, class OK",
|
|
9292
9262
|
"action.server-update.title": "Server update",
|
|
9293
9263
|
"action.server-update.started": "Updating server on {{nodeId}}…",
|
|
9294
9264
|
"action.server-update.restarting": "Server update staged on {{nodeId}} — the node is restarting. You will hear when it comes back.",
|
|
@@ -9694,6 +9664,7 @@ var it_default = {
|
|
|
9694
9664
|
"button.verdict.correct": "Corretto",
|
|
9695
9665
|
"button.verdict.wrong": "Sbagliato",
|
|
9696
9666
|
"button.verdict.wrongAs": "Sbagliato: è {{class}}",
|
|
9667
|
+
"button.verdict.wrongLabel": "Etichetta sbagliata, classe giusta",
|
|
9697
9668
|
"action.server-update.title": "Aggiornamento server",
|
|
9698
9669
|
"action.server-update.started": "Aggiornamento server su {{nodeId}}…",
|
|
9699
9670
|
"action.server-update.restarting": "Aggiornamento server predisposto su {{nodeId}} — il nodo si riavvia. Ti avviseremo quando torna.",
|
|
@@ -39079,6 +39050,11 @@ function rowIsDurableRetrainCopy(data) {
|
|
|
39079
39050
|
* audit resolves ownership from INDEX rows, but a future filesystem-side tool
|
|
39080
39051
|
* must be able to tell the two corpora apart by path alone. */
|
|
39081
39052
|
var RETRAIN_PATH_PREFIX = "retrain";
|
|
39053
|
+
/** The stored verdict subject, validated; anything unreadable reads as absent. */
|
|
39054
|
+
function narrowVerdictSubject(value) {
|
|
39055
|
+
const parsed = require_clip_model_registry.RetrainVerdictSubjectSchema.safeParse(value);
|
|
39056
|
+
return parsed.success ? { verdictSubject: parsed.data } : {};
|
|
39057
|
+
}
|
|
39082
39058
|
/** Ids per `IN (…)` page in {@link RetrainFrameStore.getMany}. */
|
|
39083
39059
|
var GET_MANY_PAGE = 500;
|
|
39084
39060
|
/**
|
|
@@ -39162,7 +39138,8 @@ var RetrainFrameStore = class {
|
|
|
39162
39138
|
complete: data["complete"] === true || data["complete"] === 1,
|
|
39163
39139
|
...finiteNumber(data["epochMs"]) !== void 0 ? { epochMs: finiteNumber(data["epochMs"]) } : {},
|
|
39164
39140
|
revision: finiteNumber(data["revision"]) ?? 0,
|
|
39165
|
-
dismissed: narrowDismissed(data["dismissed"])
|
|
39141
|
+
dismissed: narrowDismissed(data["dismissed"]),
|
|
39142
|
+
...narrowVerdictSubject(data["verdictSubject"])
|
|
39166
39143
|
};
|
|
39167
39144
|
}
|
|
39168
39145
|
/** The copies with these ids, in the order given; unknown ids are skipped. */
|
|
@@ -39515,7 +39492,8 @@ var RetrainFrameStore = class {
|
|
|
39515
39492
|
capturedAt: input.epochMs,
|
|
39516
39493
|
...chromaSpread !== void 0 ? { chromaSpread } : {},
|
|
39517
39494
|
complete: false,
|
|
39518
|
-
epochMs: input.epochMs
|
|
39495
|
+
epochMs: input.epochMs,
|
|
39496
|
+
...input.verdictSubject !== void 0 ? { verdictSubject: input.verdictSubject } : {}
|
|
39519
39497
|
};
|
|
39520
39498
|
const path = framePath(frame);
|
|
39521
39499
|
const persist = async () => {
|
|
@@ -39539,7 +39517,8 @@ var RetrainFrameStore = class {
|
|
|
39539
39517
|
chromaSpread: frame.chromaSpread ?? null,
|
|
39540
39518
|
complete: false,
|
|
39541
39519
|
origin: "recording",
|
|
39542
|
-
epochMs: input.epochMs
|
|
39520
|
+
epochMs: input.epochMs,
|
|
39521
|
+
...input.verdictSubject !== void 0 ? { verdictSubject: input.verdictSubject } : {}
|
|
39543
39522
|
});
|
|
39544
39523
|
await this.ledger.transact([row, ...withRow]);
|
|
39545
39524
|
};
|
|
@@ -39576,6 +39555,22 @@ var RetrainFrameStore = class {
|
|
|
39576
39555
|
};
|
|
39577
39556
|
}
|
|
39578
39557
|
/**
|
|
39558
|
+
* Record the verdict subject on a still that already existed (the moment was
|
|
39559
|
+
* in the dataset before the operator tapped). The latest answer wins, like
|
|
39560
|
+
* the verdict ledger.
|
|
39561
|
+
*
|
|
39562
|
+
* @returns the updated frame, or `null` when no such copy exists.
|
|
39563
|
+
*/
|
|
39564
|
+
async setVerdictSubject(frameId, verdictSubject) {
|
|
39565
|
+
const existing = await this.get(frameId);
|
|
39566
|
+
if (existing === null) return null;
|
|
39567
|
+
await this.ledger.update(frameId, { verdictSubject });
|
|
39568
|
+
return {
|
|
39569
|
+
...existing,
|
|
39570
|
+
verdictSubject
|
|
39571
|
+
};
|
|
39572
|
+
}
|
|
39573
|
+
/**
|
|
39579
39574
|
* The operator's "every subject in this frame is boxed" (D785 context: an
|
|
39580
39575
|
* unboxed subject is a false positive to a detector evaluation). Only the
|
|
39581
39576
|
* operator sets it; nothing derives it from the annotation count, because a
|
|
@@ -68515,6 +68510,7 @@ function wireSubscriptions(deps) {
|
|
|
68515
68510
|
deps.onClusterTopology(nodes);
|
|
68516
68511
|
}));
|
|
68517
68512
|
for (const category of NC_SYSTEM_EVENT_CATEGORIES) unsubs.push(deps.eventBus.subscribe({ category }, (ev) => deps.onSystemEvent(ev)));
|
|
68513
|
+
for (const category of require_clip_model_registry.ACTION_TARGET_INVALIDATION_EVENTS) unsubs.push(deps.eventBus.subscribe({ category }, () => deps.onDeviceRegistryChange()));
|
|
68518
68514
|
}
|
|
68519
68515
|
unsubs.push(deps.eventBus.subscribe({ category: require_clip_model_registry.EventCategory.DeviceBindingsChanged }, (ev) => {
|
|
68520
68516
|
deps.onBindingsChanged(ev.data);
|
|
@@ -69076,6 +69072,12 @@ async function composeEventKindsForDevices(deviceIds, composeOne, onError) {
|
|
|
69076
69072
|
* nearest one is normally the same detection; the delta is reported so a
|
|
69077
69073
|
* reviewer can see how far it was.
|
|
69078
69074
|
*
|
|
69075
|
+
* 0. **The subject a notification verdict froze** (a recording still landed by
|
|
69076
|
+
* a verdict whose track was already gone). It is the one subject the
|
|
69077
|
+
* operator answered about, so it comes FIRST and is the own box; the
|
|
69078
|
+
* neighbours below stay secondary. Its box is normalised against the full
|
|
69079
|
+
* frame already, so it is used as is — never re-scaled.
|
|
69080
|
+
*
|
|
69079
69081
|
* Every OTHER track alive on the camera at that instant contributes its own
|
|
69080
69082
|
* nearest position the same way — a frame with three people is three boxes,
|
|
69081
69083
|
* and a recording still (no track of its own) gets everything the pipeline
|
|
@@ -69130,6 +69132,18 @@ function toRetrainClassTiers(className, label) {
|
|
|
69130
69132
|
label: kept
|
|
69131
69133
|
} : { macroClass };
|
|
69132
69134
|
}
|
|
69135
|
+
/** A verdict on the wire: the operator's class split into tiers once, here. */
|
|
69136
|
+
function toRetrainVerdict(input) {
|
|
69137
|
+
const tiers = input.suggestedClass !== void 0 ? toRetrainClassTiers(input.suggestedClass, void 0) : null;
|
|
69138
|
+
return {
|
|
69139
|
+
verdict: input.verdict,
|
|
69140
|
+
trackId: input.trackId,
|
|
69141
|
+
...input.suggestedClass !== void 0 ? { suggestedClass: input.suggestedClass } : {},
|
|
69142
|
+
...tiers !== null ? { suggestedMacroClass: tiers.macroClass } : {},
|
|
69143
|
+
...tiers?.label !== void 0 ? { suggestedLabel: tiers.label } : {},
|
|
69144
|
+
...input.at !== void 0 ? { at: input.at } : {}
|
|
69145
|
+
};
|
|
69146
|
+
}
|
|
69133
69147
|
/** A pixel box as a clamped normalised one, or `null` when nothing is left. */
|
|
69134
69148
|
function normalise(box, frameWidth, frameHeight) {
|
|
69135
69149
|
if (frameWidth === void 0 || frameHeight === void 0) return null;
|
|
@@ -69187,6 +69201,22 @@ function boxFor(track, bbox, basis, deltaMs, score) {
|
|
|
69187
69201
|
deltaMs
|
|
69188
69202
|
};
|
|
69189
69203
|
}
|
|
69204
|
+
/** The box a notification verdict froze, or `null` when the event carried none. */
|
|
69205
|
+
function boxOfVerdict(subject) {
|
|
69206
|
+
const box = subject.bbox;
|
|
69207
|
+
if (box === void 0) return null;
|
|
69208
|
+
const clamped = normalise(box, 1, 1);
|
|
69209
|
+
if (clamped === null) return null;
|
|
69210
|
+
const tiers = toRetrainClassTiers(subject.className, subject.label);
|
|
69211
|
+
return {
|
|
69212
|
+
bbox: clamped,
|
|
69213
|
+
macroClass: tiers.macroClass,
|
|
69214
|
+
...tiers.label !== void 0 ? { label: tiers.label } : {},
|
|
69215
|
+
trackId: subject.trackId,
|
|
69216
|
+
basis: "verdict",
|
|
69217
|
+
deltaMs: 0
|
|
69218
|
+
};
|
|
69219
|
+
}
|
|
69190
69220
|
/** The own track's box: the recorded subject box first, the nearest position second. */
|
|
69191
69221
|
function ownBox(input, tolerance) {
|
|
69192
69222
|
const track = input.ownTrack;
|
|
@@ -69209,10 +69239,13 @@ function pipelineBoxesForFrame(input) {
|
|
|
69209
69239
|
const out = [];
|
|
69210
69240
|
const own = ownBox(input, tolerance);
|
|
69211
69241
|
if (own !== null) out.push(own);
|
|
69242
|
+
const verdictBox = input.verdictSubject !== void 0 ? boxOfVerdict(input.verdictSubject) : null;
|
|
69243
|
+
if (verdictBox !== null) out.push(verdictBox);
|
|
69212
69244
|
const at = input.frame.capturedAt;
|
|
69213
69245
|
if (at === void 0) return out;
|
|
69214
69246
|
const ownId = input.frame.trackId;
|
|
69215
69247
|
const seen = new Set(ownId !== void 0 ? [ownId] : []);
|
|
69248
|
+
if (input.verdictSubject !== void 0) seen.add(input.verdictSubject.trackId);
|
|
69216
69249
|
for (const track of input.neighbours) {
|
|
69217
69250
|
if (seen.has(track.trackId) || track.deviceId !== input.frame.deviceId) continue;
|
|
69218
69251
|
seen.add(track.trackId);
|
|
@@ -82462,10 +82495,18 @@ function parseActions(raw) {
|
|
|
82462
82495
|
for (const entry of raw) {
|
|
82463
82496
|
const parsed = require_clip_model_registry.NcRuleActionSchema.safeParse(entry);
|
|
82464
82497
|
if (!parsed.success) return null;
|
|
82465
|
-
out.push(parsed.data);
|
|
82498
|
+
out.push(require_clip_model_registry.normalizeNcAction(parsed.data));
|
|
82466
82499
|
}
|
|
82467
82500
|
return out;
|
|
82468
82501
|
}
|
|
82502
|
+
/** Does a stored row still carry a pre-D983 cap step (`deviceId`, no `target`)? */
|
|
82503
|
+
function hasLegacyTarget(raw) {
|
|
82504
|
+
if (!Array.isArray(raw)) return false;
|
|
82505
|
+
return raw.some((entry) => {
|
|
82506
|
+
const parsed = require_clip_model_registry.NcRuleActionSchema.safeParse(entry);
|
|
82507
|
+
return parsed.success && parsed.data.kind === "cap" && parsed.data.target === void 0;
|
|
82508
|
+
});
|
|
82509
|
+
}
|
|
82469
82510
|
function rowToSequence(id, body) {
|
|
82470
82511
|
const actions = parseActions(body.actions);
|
|
82471
82512
|
if (actions === null) return null;
|
|
@@ -82504,6 +82545,7 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82504
82545
|
try {
|
|
82505
82546
|
const { records } = await this.kind.query({ limit: 1e4 });
|
|
82506
82547
|
const next = /* @__PURE__ */ new Map();
|
|
82548
|
+
const legacyRows = [];
|
|
82507
82549
|
let skipped = 0;
|
|
82508
82550
|
for (const row of records) {
|
|
82509
82551
|
const sequence = rowToSequence(row.id, row.data);
|
|
@@ -82512,9 +82554,11 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82512
82554
|
continue;
|
|
82513
82555
|
}
|
|
82514
82556
|
next.set(sequence.id, sequence);
|
|
82557
|
+
if (hasLegacyTarget(row.data.actions)) legacyRows.push(row.id);
|
|
82515
82558
|
}
|
|
82516
82559
|
this.byId.clear();
|
|
82517
82560
|
for (const [id, seq] of next) this.byId.set(id, seq);
|
|
82561
|
+
await this.rewriteLegacyRows(legacyRows, records);
|
|
82518
82562
|
if (skipped > 0) this.logger.warn("sequence rows skipped as malformed — bindings to them will miss", { meta: { skipped } });
|
|
82519
82563
|
} catch (err) {
|
|
82520
82564
|
this.logger.warn("sequence load failed — keeping the sequences already in memory", { meta: {
|
|
@@ -82523,6 +82567,28 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82523
82567
|
} });
|
|
82524
82568
|
}
|
|
82525
82569
|
}
|
|
82570
|
+
/**
|
|
82571
|
+
* One-shot, idempotent (D983): a row whose steps still carry `deviceId` is
|
|
82572
|
+
* stored again in the target shape. A failed write leaves the row as it was —
|
|
82573
|
+
* it is still READ as the new shape — and is on the record.
|
|
82574
|
+
*/
|
|
82575
|
+
async rewriteLegacyRows(ids, records) {
|
|
82576
|
+
for (const id of ids) {
|
|
82577
|
+
const sequence = this.byId.get(id) ?? null;
|
|
82578
|
+
const row = records.find((r) => r.id === id);
|
|
82579
|
+
if (sequence === null || row === void 0) continue;
|
|
82580
|
+
try {
|
|
82581
|
+
const createdAt = row.data.createdAt;
|
|
82582
|
+
await this.persist(sequence, typeof createdAt === "number" && Number.isFinite(createdAt) ? createdAt : this.now(), this.now());
|
|
82583
|
+
this.logger.info("notification sequence rewritten to the target shape", { meta: { sequenceId: id } });
|
|
82584
|
+
} catch (err) {
|
|
82585
|
+
this.logger.warn("notification sequence could not be rewritten — it is still read as the target shape", { meta: {
|
|
82586
|
+
sequenceId: id,
|
|
82587
|
+
error: String(err)
|
|
82588
|
+
} });
|
|
82589
|
+
}
|
|
82590
|
+
}
|
|
82591
|
+
}
|
|
82526
82592
|
list() {
|
|
82527
82593
|
return [...this.byId.values()].toSorted((a, b) => a.name.localeCompare(b.name));
|
|
82528
82594
|
}
|
|
@@ -82542,7 +82608,7 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82542
82608
|
id: (0, node_crypto.randomUUID)(),
|
|
82543
82609
|
name: input.name,
|
|
82544
82610
|
...input.minDelaySec !== void 0 ? { minDelaySec: input.minDelaySec } : {},
|
|
82545
|
-
actions:
|
|
82611
|
+
actions: input.actions.map(require_clip_model_registry.normalizeNcAction)
|
|
82546
82612
|
};
|
|
82547
82613
|
await this.persist(sequence, now, now);
|
|
82548
82614
|
this.byId.set(sequence.id, sequence);
|
|
@@ -82558,7 +82624,7 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82558
82624
|
id: sequence.id,
|
|
82559
82625
|
name: sequence.name,
|
|
82560
82626
|
...sequence.minDelaySec !== void 0 ? { minDelaySec: sequence.minDelaySec } : {},
|
|
82561
|
-
actions:
|
|
82627
|
+
actions: sequence.actions.map(require_clip_model_registry.normalizeNcAction)
|
|
82562
82628
|
};
|
|
82563
82629
|
await this.persist(stored, now, now);
|
|
82564
82630
|
this.byId.set(stored.id, stored);
|
|
@@ -82571,7 +82637,7 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82571
82637
|
id: sequenceId,
|
|
82572
82638
|
name: input.name,
|
|
82573
82639
|
...input.minDelaySec !== void 0 ? { minDelaySec: input.minDelaySec } : {},
|
|
82574
|
-
actions:
|
|
82640
|
+
actions: input.actions.map(require_clip_model_registry.normalizeNcAction)
|
|
82575
82641
|
};
|
|
82576
82642
|
const createdAt = await this.readCreatedAt(sequenceId, now);
|
|
82577
82643
|
await this.persist(sequence, createdAt, now);
|
|
@@ -82639,7 +82705,11 @@ var NC_DETECTION_VERDICTS_COLUMNS = [
|
|
|
82639
82705
|
var NcDetectionVerdictRecordSchema = require_clip_model_registry.object({
|
|
82640
82706
|
trackId: require_clip_model_registry.string().min(1),
|
|
82641
82707
|
deviceId: require_clip_model_registry.number().int(),
|
|
82642
|
-
verdict: require_clip_model_registry._enum([
|
|
82708
|
+
verdict: require_clip_model_registry._enum([
|
|
82709
|
+
"correct",
|
|
82710
|
+
"wrong",
|
|
82711
|
+
"wrong-label"
|
|
82712
|
+
]),
|
|
82643
82713
|
/** The class the operator said it really was. Only with `wrong`. */
|
|
82644
82714
|
suggestedClass: require_clip_model_registry.string().min(1).optional(),
|
|
82645
82715
|
/** Whose notification it was (the token's frozen owner), NOT who tapped. */
|
|
@@ -82674,6 +82744,16 @@ var NcDetectionVerdictLedger = class {
|
|
|
82674
82744
|
logger: deps.logger
|
|
82675
82745
|
});
|
|
82676
82746
|
}
|
|
82747
|
+
/** The answers recorded for these tracks (a track nobody answered is absent).
|
|
82748
|
+
* Reads the in-memory mirror: no I/O, so a listing never waits on it. */
|
|
82749
|
+
forTracks(trackIds) {
|
|
82750
|
+
const out = /* @__PURE__ */ new Map();
|
|
82751
|
+
for (const id of trackIds) {
|
|
82752
|
+
const row = this.ledger.get(id);
|
|
82753
|
+
if (row !== void 0) out.set(id, row);
|
|
82754
|
+
}
|
|
82755
|
+
return out;
|
|
82756
|
+
}
|
|
82677
82757
|
/** Write-through; throws on a failed write so the tap reports it. */
|
|
82678
82758
|
async record(row) {
|
|
82679
82759
|
await this.ledger.put(NcDetectionVerdictRecordSchema.parse(row));
|
|
@@ -82807,10 +82887,33 @@ function pruneHookBindings(bindings, field, isDead, notes) {
|
|
|
82807
82887
|
kept.push(binding);
|
|
82808
82888
|
continue;
|
|
82809
82889
|
}
|
|
82810
|
-
if (binding.action.kind === "cap"
|
|
82811
|
-
|
|
82812
|
-
|
|
82813
|
-
|
|
82890
|
+
if (binding.action.kind === "cap") {
|
|
82891
|
+
const { target } = require_clip_model_registry.normalizeNcCapTarget(binding.action);
|
|
82892
|
+
if (target.kind === "devices") {
|
|
82893
|
+
const dead = target.ids.filter(isDead);
|
|
82894
|
+
if (dead.length === target.ids.length) {
|
|
82895
|
+
changed = true;
|
|
82896
|
+
notes.removed(field, deviceSubjects(dead), "binding-removed");
|
|
82897
|
+
continue;
|
|
82898
|
+
}
|
|
82899
|
+
if (dead.length > 0) {
|
|
82900
|
+
changed = true;
|
|
82901
|
+
notes.removed(field, deviceSubjects(dead), "binding-removed");
|
|
82902
|
+
const alive = target.ids.filter((id) => !isDead(id));
|
|
82903
|
+
kept.push({
|
|
82904
|
+
...binding,
|
|
82905
|
+
action: require_clip_model_registry.normalizeNcAction({
|
|
82906
|
+
...binding.action,
|
|
82907
|
+
deviceId: void 0,
|
|
82908
|
+
target: {
|
|
82909
|
+
kind: "devices",
|
|
82910
|
+
ids: alive
|
|
82911
|
+
}
|
|
82912
|
+
})
|
|
82913
|
+
});
|
|
82914
|
+
continue;
|
|
82915
|
+
}
|
|
82916
|
+
}
|
|
82814
82917
|
}
|
|
82815
82918
|
kept.push(binding);
|
|
82816
82919
|
}
|
|
@@ -82966,7 +83069,11 @@ function referencedDeviceIds(snapshot) {
|
|
|
82966
83069
|
add(c.systemEvent?.deviceIds);
|
|
82967
83070
|
if (c.deviceState !== void 0) ids.add(c.deviceState.deviceId);
|
|
82968
83071
|
if (c.scene?.deviceId !== void 0) ids.add(c.scene.deviceId);
|
|
82969
|
-
for (const binding of [...rule.actions?.onTrigger ?? [], ...rule.actions?.onTriggerOff ?? []])
|
|
83072
|
+
for (const binding of [...rule.actions?.onTrigger ?? [], ...rule.actions?.onTriggerOff ?? []]) {
|
|
83073
|
+
if (binding.kind !== "do" || binding.action.kind !== "cap") continue;
|
|
83074
|
+
const { target } = require_clip_model_registry.normalizeNcCapTarget(binding.action);
|
|
83075
|
+
if (target.kind === "devices") add(target.ids);
|
|
83076
|
+
}
|
|
82970
83077
|
}
|
|
82971
83078
|
for (const rule of snapshot.timelapse) add(rule.deviceIds);
|
|
82972
83079
|
for (const rule of snapshot.summary) add(rule.deviceIds);
|
|
@@ -83637,6 +83744,8 @@ var NcDeviceStateCache = class {
|
|
|
83637
83744
|
//#region src/notification-center/detection-verdict-buttons.ts
|
|
83638
83745
|
var VERDICT_CORRECT_ID = "detection-verdict-correct";
|
|
83639
83746
|
var VERDICT_WRONG_ID = "detection-verdict-wrong";
|
|
83747
|
+
/** Wrong LABEL, right class — only for a subject that has a label. */
|
|
83748
|
+
var VERDICT_WRONG_LABEL_ID = "detection-verdict-wrong-label";
|
|
83640
83749
|
/** `detection-verdict-wrong:<class>` — Wrong, and what it really was. */
|
|
83641
83750
|
var VERDICT_WRONG_AS_PREFIX = "detection-verdict-wrong:";
|
|
83642
83751
|
/** The macro classes offered as "it was really…". Short on purpose. */
|
|
@@ -83653,6 +83762,15 @@ function verdictButtonRefusal(subject) {
|
|
|
83653
83762
|
if (subject.ownerUserId === void 0) return "unowned-target";
|
|
83654
83763
|
return null;
|
|
83655
83764
|
}
|
|
83765
|
+
/**
|
|
83766
|
+
* Does the notified subject carry a label below its macro class — a sub-class
|
|
83767
|
+
* (`dog` under `animal`), a recognised name, a plate? The same tier split the
|
|
83768
|
+
* retrain annotations use, so "has a label" means one thing in both places.
|
|
83769
|
+
*/
|
|
83770
|
+
function subjectHasLabel(className, label) {
|
|
83771
|
+
if (className === void 0) return false;
|
|
83772
|
+
return toRetrainClassTiers(className, label).label !== void 0;
|
|
83773
|
+
}
|
|
83656
83774
|
/** The classes worth offering for a subject of `className`. */
|
|
83657
83775
|
function verdictClassChoices(className) {
|
|
83658
83776
|
return VERDICT_CLASS_CHOICES.filter((c) => c !== className).slice(0, VERDICT_MAX_CLASS_CHOICES);
|
|
@@ -83664,13 +83782,17 @@ function verdictClassChoices(className) {
|
|
|
83664
83782
|
* when the channel declared room for a fourth button.
|
|
83665
83783
|
*/
|
|
83666
83784
|
function verdictButtonSpecs(input) {
|
|
83667
|
-
const
|
|
83785
|
+
const plain = [{
|
|
83668
83786
|
actionId: VERDICT_CORRECT_ID,
|
|
83669
83787
|
verdict: "correct"
|
|
83670
83788
|
}, {
|
|
83671
83789
|
actionId: VERDICT_WRONG_ID,
|
|
83672
83790
|
verdict: "wrong"
|
|
83673
83791
|
}];
|
|
83792
|
+
const base = subjectHasLabel(input.className, input.label) ? [...plain, {
|
|
83793
|
+
actionId: VERDICT_WRONG_LABEL_ID,
|
|
83794
|
+
verdict: "wrong-label"
|
|
83795
|
+
}] : plain;
|
|
83674
83796
|
if (input.maxActions === void 0 || input.maxActions <= 3) return base;
|
|
83675
83797
|
const room = input.maxActions - base.length;
|
|
83676
83798
|
const extra = verdictClassChoices(input.className).slice(0, room).map((suggestedClass) => ({
|
|
@@ -87401,6 +87523,8 @@ var NcDispatcher = class {
|
|
|
87401
87523
|
...target.ownerUserId !== void 0 ? { ownerUserId: target.ownerUserId } : {},
|
|
87402
87524
|
...entry.payload.subject.className !== void 0 ? { className: entry.payload.subject.className } : {},
|
|
87403
87525
|
...entry.payload.subject.trackId !== void 0 ? { trackId: entry.payload.subject.trackId } : {},
|
|
87526
|
+
...entry.payload.subject.label !== void 0 ? { label: entry.payload.subject.label } : {},
|
|
87527
|
+
...entry.payload.subject.bbox !== void 0 ? { bbox: entry.payload.subject.bbox } : {},
|
|
87404
87528
|
eventAtMs: entry.payload.subject.timestamp,
|
|
87405
87529
|
...target.maxActions !== void 0 ? { maxActions: target.maxActions } : {},
|
|
87406
87530
|
...entry.payload.subject.systemEvent !== void 0 ? { systemEvent: entry.payload.subject.systemEvent } : {}
|
|
@@ -91764,7 +91888,7 @@ var NcRuleActionRunner = class {
|
|
|
91764
91888
|
if (sequence.throttle) this.lastRunAt.set(sequence.id, now);
|
|
91765
91889
|
let stepsRun = 0;
|
|
91766
91890
|
for (const [index, action] of sequence.actions.entries()) try {
|
|
91767
|
-
await this.step(action, input.origin);
|
|
91891
|
+
await this.step(action, input.origin, sequence.id);
|
|
91768
91892
|
stepsRun += 1;
|
|
91769
91893
|
} catch (err) {
|
|
91770
91894
|
this.deps.logger.warn("rule action failed — stopping the sequence", {
|
|
@@ -91804,20 +91928,28 @@ var NcRuleActionRunner = class {
|
|
|
91804
91928
|
stepsRun
|
|
91805
91929
|
};
|
|
91806
91930
|
}
|
|
91807
|
-
async step(action, origin) {
|
|
91931
|
+
async step(action, origin, owner) {
|
|
91808
91932
|
if (action.kind === "wait") {
|
|
91809
91933
|
await this.sleep(action.seconds * 1e3);
|
|
91810
91934
|
return;
|
|
91811
91935
|
}
|
|
91812
91936
|
if (!this.deps.isDeviceScoped(action.cap)) throw new Error(`rule action refused: "${action.cap}" is not a device capability — only devices can be actuated`);
|
|
91813
|
-
|
|
91814
|
-
|
|
91937
|
+
const { target, actionId } = require_clip_model_registry.normalizeNcCapTarget(action);
|
|
91938
|
+
const deviceIds = await this.devicesOf(target, actionId, owner);
|
|
91939
|
+
for (const deviceId of deviceIds) await this.deps.actuate({
|
|
91940
|
+
deviceId,
|
|
91815
91941
|
cap: action.cap,
|
|
91816
91942
|
method: action.method,
|
|
91817
91943
|
args: action.args ?? {},
|
|
91818
91944
|
...origin !== void 0 ? { origin } : {}
|
|
91819
91945
|
});
|
|
91820
91946
|
}
|
|
91947
|
+
async devicesOf(target, actionId, owner) {
|
|
91948
|
+
if (target.kind === "devices") return target.ids;
|
|
91949
|
+
if (target.kind === "var") throw new Error("rule action refused: a sequence has no variables to resolve a target from");
|
|
91950
|
+
if (this.deps.resolveTarget === void 0) throw new Error("rule action refused: this node cannot resolve a domain target");
|
|
91951
|
+
return (await this.deps.resolveTarget(target, actionId, owner)).deviceIds;
|
|
91952
|
+
}
|
|
91821
91953
|
};
|
|
91822
91954
|
/** Force-enable a shared sequence for a button tap (always runs, still throttled). */
|
|
91823
91955
|
function asForcedSequence(sequence) {
|
|
@@ -100205,6 +100337,12 @@ function rowRefusalId(systemEvent) {
|
|
|
100205
100337
|
const value = systemEvent?.data["refusalId"];
|
|
100206
100338
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
100207
100339
|
}
|
|
100340
|
+
/** The text-catalog key of each verdict button. */
|
|
100341
|
+
var VERDICT_LABEL_KEY = {
|
|
100342
|
+
correct: "button.verdict.correct",
|
|
100343
|
+
wrong: "button.verdict.wrong",
|
|
100344
|
+
"wrong-label": "button.verdict.wrongLabel"
|
|
100345
|
+
};
|
|
100208
100346
|
/**
|
|
100209
100347
|
* The tap-through buttons for ONE delivery, each with its own single-use
|
|
100210
100348
|
* token.
|
|
@@ -100241,6 +100379,7 @@ async function mintButtons(host, input) {
|
|
|
100241
100379
|
const verdictOwner = input.ownerUserId;
|
|
100242
100380
|
const verdictButtons = verdictRefusal !== null || verdictTrackId === void 0 || verdictOwner === void 0 ? [] : verdictButtonSpecs({
|
|
100243
100381
|
className: input.className,
|
|
100382
|
+
label: input.label,
|
|
100244
100383
|
maxActions: input.maxActions
|
|
100245
100384
|
}).map((spec) => {
|
|
100246
100385
|
const id = (0, node_crypto.randomUUID)();
|
|
@@ -100258,6 +100397,11 @@ async function mintButtons(host, input) {
|
|
|
100258
100397
|
...spec.suggestedClass !== void 0 ? { suggestedClass: spec.suggestedClass } : {},
|
|
100259
100398
|
...input.maxActions === 0 ? { viaLink: true } : {},
|
|
100260
100399
|
...input.eventAtMs !== void 0 ? { eventAtMs: input.eventAtMs } : {},
|
|
100400
|
+
...input.className !== void 0 ? { subject: {
|
|
100401
|
+
className: input.className,
|
|
100402
|
+
...input.label !== void 0 ? { label: input.label } : {},
|
|
100403
|
+
...input.bbox !== void 0 ? { bbox: input.bbox } : {}
|
|
100404
|
+
} } : {},
|
|
100261
100405
|
deviceId: input.deviceId,
|
|
100262
100406
|
actionId: spec.actionId,
|
|
100263
100407
|
targetId: input.targetId,
|
|
@@ -100266,7 +100410,7 @@ async function mintButtons(host, input) {
|
|
|
100266
100410
|
});
|
|
100267
100411
|
return {
|
|
100268
100412
|
id: spec.actionId,
|
|
100269
|
-
label: spec.suggestedClass === void 0 ? host.texts.text({ key: spec.verdict
|
|
100413
|
+
label: spec.suggestedClass === void 0 ? host.texts.text({ key: VERDICT_LABEL_KEY[spec.verdict] }) : host.texts.text({
|
|
100270
100414
|
key: "button.verdict.wrongAs",
|
|
100271
100415
|
vars: { class: spec.suggestedClass }
|
|
100272
100416
|
}),
|
|
@@ -100725,6 +100869,18 @@ var VERDICT_FRAME_REFUSAL_COPY = {
|
|
|
100725
100869
|
"still-busy": "The track is gone and the recorder is busy — nothing was changed, try again later",
|
|
100726
100870
|
failed: "The track is gone and its frame could not be saved — nothing was changed"
|
|
100727
100871
|
};
|
|
100872
|
+
/** The grant's frozen subject, in the shape the retrain still stores it in. */
|
|
100873
|
+
function verdictSubjectOf(grant, subject, at) {
|
|
100874
|
+
return {
|
|
100875
|
+
trackId: grant.trackId,
|
|
100876
|
+
className: subject.className,
|
|
100877
|
+
...subject.label !== void 0 ? { label: subject.label } : {},
|
|
100878
|
+
...subject.bbox !== void 0 ? { bbox: subject.bbox } : {},
|
|
100879
|
+
verdict: grant.verdict,
|
|
100880
|
+
...grant.suggestedClass !== void 0 ? { suggestedClass: grant.suggestedClass } : {},
|
|
100881
|
+
at
|
|
100882
|
+
};
|
|
100883
|
+
}
|
|
100728
100884
|
/**
|
|
100729
100885
|
* The track is gone (aged out, or discarded as a still object): the camera's
|
|
100730
100886
|
* RECORDING still covers the instant, so the verdict lands a frame there
|
|
@@ -100747,7 +100903,8 @@ async function landFrameFromRecording(host, grant) {
|
|
|
100747
100903
|
}
|
|
100748
100904
|
const landed = await addFrame({
|
|
100749
100905
|
deviceId: grant.deviceId,
|
|
100750
|
-
epochMs: grant.eventAtMs
|
|
100906
|
+
epochMs: grant.eventAtMs,
|
|
100907
|
+
...grant.subject !== void 0 ? { subject: verdictSubjectOf(grant, grant.subject, host.now()) } : {}
|
|
100751
100908
|
});
|
|
100752
100909
|
if (!landed.ok) {
|
|
100753
100910
|
host.logger.warn("detection verdict dropped — the track is gone and the recording refused", {
|
|
@@ -101708,6 +101865,7 @@ function buildEntries(host, rule, subject, kind, matchedOn, userTargets = [], or
|
|
|
101708
101865
|
...alarmSource?.eventId !== void 0 ? { eventId: alarmSource.eventId } : hasEventMedia ? { eventId: subject.recordId } : {},
|
|
101709
101866
|
className: subject.classNames[0] ?? subject.sensorKind ?? "event",
|
|
101710
101867
|
...subject.label !== void 0 ? { label: subject.label } : {},
|
|
101868
|
+
...subject.bbox !== void 0 ? { bbox: subject.bbox } : {},
|
|
101711
101869
|
...subject.labelKind !== void 0 ? { labelKind: subject.labelKind } : {},
|
|
101712
101870
|
...subject.confidence !== void 0 ? { confidence: subject.confidence } : {},
|
|
101713
101871
|
zones: renderedZones,
|
|
@@ -106028,6 +106186,8 @@ var NotificationCenter = class {
|
|
|
106028
106186
|
zoneOwners;
|
|
106029
106187
|
/** Runs a matched rule's action sequences. Null when no actuator is wired. */
|
|
106030
106188
|
actionRunner;
|
|
106189
|
+
/** Domain action targets read from the registry (D983); null without a directory. */
|
|
106190
|
+
actionTargets;
|
|
106031
106191
|
/** Grants behind the tap-through buttons. In-process and single-use — read
|
|
106032
106192
|
* `action-token.ts` before assuming anything about what a tap proves. */
|
|
106033
106193
|
actionTokens = new NcActionTokenRegistry();
|
|
@@ -106435,9 +106595,15 @@ var NotificationCenter = class {
|
|
|
106435
106595
|
});
|
|
106436
106596
|
this.livenessHoldoff = new DeviceLivenessHoldoff((event) => this.consumeEvent(event), { delayMs: deps.livenessHoldoffMs ?? (process.env.VITEST ? 0 : DEVICE_LIVENESS_HOLDOFF_MS) });
|
|
106437
106597
|
this.nodeLivenessHoldoff = new DeviceLivenessHoldoff((event, timing) => this.consumeNodeLiveness(event, timing), { delayMs: deps.nodeLivenessHoldoffMs ?? (process.env.VITEST ? 0 : NODE_LIVENESS_HOLDOFF_MS) });
|
|
106598
|
+
const targets = deps.actionTargetDirectory !== void 0 ? new require_clip_model_registry.ActionTargetResolver(deps.actionTargetDirectory, this.logger.child("actions")) : null;
|
|
106599
|
+
this.actionTargets = targets;
|
|
106438
106600
|
this.actionRunner = deps.actuate !== void 0 && deps.isCapDeviceScoped !== void 0 ? new NcRuleActionRunner({
|
|
106439
106601
|
logger: this.logger.child("actions"),
|
|
106440
106602
|
actuate: (input) => this.actuateWithAttribution(input),
|
|
106603
|
+
...targets !== null ? { resolveTarget: (target, actionId, owner) => targets.resolve(target, actionId, {}, {
|
|
106604
|
+
actionId,
|
|
106605
|
+
owner
|
|
106606
|
+
}) } : {},
|
|
106441
106607
|
isDeviceScoped: deps.isCapDeviceScoped,
|
|
106442
106608
|
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
106443
106609
|
}) : null;
|
|
@@ -107227,6 +107393,21 @@ var NotificationCenter = class {
|
|
|
107227
107393
|
}
|
|
107228
107394
|
};
|
|
107229
107395
|
}
|
|
107396
|
+
/**
|
|
107397
|
+
* What the operator answered to the "was this detection right?" buttons, for
|
|
107398
|
+
* the retrain page: by track, the latest answer wins. A track nobody answered
|
|
107399
|
+
* is absent.
|
|
107400
|
+
*/
|
|
107401
|
+
detectionVerdictsOf(trackIds) {
|
|
107402
|
+
const out = /* @__PURE__ */ new Map();
|
|
107403
|
+
for (const [trackId, row] of this.verdicts.forTracks(trackIds)) out.set(trackId, toRetrainVerdict({
|
|
107404
|
+
verdict: row.verdict,
|
|
107405
|
+
trackId,
|
|
107406
|
+
at: row.at,
|
|
107407
|
+
suggestedClass: row.suggestedClass
|
|
107408
|
+
}));
|
|
107409
|
+
return out;
|
|
107410
|
+
}
|
|
107230
107411
|
observeOccupancy(deviceId, snapshot, evidence = "scene") {
|
|
107231
107412
|
return observeOccupancy(this.occupancyWatchHost(), deviceId, snapshot, evidence);
|
|
107232
107413
|
}
|
|
@@ -107402,6 +107583,10 @@ var NotificationCenter = class {
|
|
|
107402
107583
|
}
|
|
107403
107584
|
};
|
|
107404
107585
|
}
|
|
107586
|
+
/** The device registry changed: a cached domain action target is stale (D983). */
|
|
107587
|
+
invalidateActionTargets() {
|
|
107588
|
+
this.actionTargets?.invalidate();
|
|
107589
|
+
}
|
|
107405
107590
|
onSystemEvent(event) {
|
|
107406
107591
|
return onSystemEvent(this.systemEventsHost(), event);
|
|
107407
107592
|
}
|
|
@@ -108875,7 +109060,9 @@ async function addVerdictFrameFromRecording(api, logger, input) {
|
|
|
108875
109060
|
}
|
|
108876
109061
|
const result = await api.addRetrainFrameFromRecording({
|
|
108877
109062
|
collectionId,
|
|
108878
|
-
|
|
109063
|
+
deviceId: input.deviceId,
|
|
109064
|
+
epochMs: input.epochMs,
|
|
109065
|
+
...input.subject !== void 0 ? { verdictSubject: input.subject } : {}
|
|
108879
109066
|
});
|
|
108880
109067
|
if (result.kind === "added") return { ok: true };
|
|
108881
109068
|
return {
|
|
@@ -109162,6 +109349,20 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
|
|
|
109162
109349
|
});
|
|
109163
109350
|
},
|
|
109164
109351
|
isCapDeviceScoped: require_clip_model_registry.isDeviceScopedCap,
|
|
109352
|
+
actionTargetDirectory: {
|
|
109353
|
+
listDeviceIdsByCaps: (capNames) => api.deviceManager.listDeviceIdsByCaps.query({ capNames: [...capNames] }),
|
|
109354
|
+
listDevices: async (deviceIds) => {
|
|
109355
|
+
return (await api.deviceManager.listAll.query({
|
|
109356
|
+
deviceIds: [...deviceIds],
|
|
109357
|
+
projection: "slim"
|
|
109358
|
+
})).map((d) => ({
|
|
109359
|
+
id: d.id,
|
|
109360
|
+
type: d.type,
|
|
109361
|
+
location: d.location,
|
|
109362
|
+
disabled: d.disabled
|
|
109363
|
+
}));
|
|
109364
|
+
}
|
|
109365
|
+
},
|
|
109165
109366
|
generateVision: async (request) => {
|
|
109166
109367
|
const result = await api.llm.generateVision.mutate({
|
|
109167
109368
|
...request.profileId !== void 0 ? { profileId: request.profileId } : {},
|
|
@@ -113787,7 +113988,7 @@ var RetrainCollectionService = class {
|
|
|
113787
113988
|
frameId: recordingFrameId(input.deviceId, input.epochMs),
|
|
113788
113989
|
deviceId: input.deviceId
|
|
113789
113990
|
});
|
|
113790
|
-
const stored = await this.storeStill(input.deviceId, input.epochMs, membershipOp === null ? [] : [membershipOp]);
|
|
113991
|
+
const stored = await this.storeStill(input.deviceId, input.epochMs, membershipOp === null ? [] : [membershipOp], input.verdictSubject);
|
|
113791
113992
|
if (stored.kind === "refused") {
|
|
113792
113993
|
this.deps.logger.info("retrain collection add refused", {
|
|
113793
113994
|
tags,
|
|
@@ -113799,11 +114000,15 @@ var RetrainCollectionService = class {
|
|
|
113799
114000
|
});
|
|
113800
114001
|
return stored;
|
|
113801
114002
|
}
|
|
113802
|
-
|
|
113803
|
-
|
|
113804
|
-
|
|
113805
|
-
|
|
113806
|
-
|
|
114003
|
+
let storedFrame = stored.frame;
|
|
114004
|
+
if (stored.reused) {
|
|
114005
|
+
await this.deps.collections.addFrame({
|
|
114006
|
+
collectionId: input.collectionId,
|
|
114007
|
+
frameId: stored.frame.frameId,
|
|
114008
|
+
deviceId: input.deviceId
|
|
114009
|
+
});
|
|
114010
|
+
if (input.verdictSubject !== void 0) storedFrame = await this.deps.frames.setVerdictSubject(stored.frame.frameId, input.verdictSubject) ?? stored.frame;
|
|
114011
|
+
}
|
|
113807
114012
|
this.deps.logger.info("retrain frame added to a collection from the recording", {
|
|
113808
114013
|
tags,
|
|
113809
114014
|
meta: {
|
|
@@ -113813,7 +114018,7 @@ var RetrainCollectionService = class {
|
|
|
113813
114018
|
reused: stored.reused
|
|
113814
114019
|
}
|
|
113815
114020
|
});
|
|
113816
|
-
const [wire] = await this.deps.retrain.toWireFrames([
|
|
114021
|
+
const [wire] = await this.deps.retrain.toWireFrames([storedFrame]);
|
|
113817
114022
|
if (wire === void 0) throw new Error("retrain: a stored still produced no wire frame");
|
|
113818
114023
|
return {
|
|
113819
114024
|
kind: "added",
|
|
@@ -113864,7 +114069,7 @@ var RetrainCollectionService = class {
|
|
|
113864
114069
|
const ids = await this.deps.collections.listFrameIds(input.collectionId);
|
|
113865
114070
|
return this.deps.retrain.toWireFrames(await this.deps.frames.getMany(ids));
|
|
113866
114071
|
}
|
|
113867
|
-
async storeStill(deviceId, epochMs, withRow) {
|
|
114072
|
+
async storeStill(deviceId, epochMs, withRow, verdictSubject) {
|
|
113868
114073
|
const existing = await this.deps.frames.get(recordingFrameId(deviceId, epochMs));
|
|
113869
114074
|
if (existing !== null) return {
|
|
113870
114075
|
kind: "stored",
|
|
@@ -113883,7 +114088,8 @@ var RetrainCollectionService = class {
|
|
|
113883
114088
|
const stored = await this.deps.frames.storeRecordingStill({
|
|
113884
114089
|
deviceId,
|
|
113885
114090
|
epochMs,
|
|
113886
|
-
bytes: still.jpeg
|
|
114091
|
+
bytes: still.jpeg,
|
|
114092
|
+
...verdictSubject !== void 0 ? { verdictSubject } : {}
|
|
113887
114093
|
}, withRow);
|
|
113888
114094
|
return stored.kind === "copied" ? {
|
|
113889
114095
|
kind: "stored",
|
|
@@ -114669,6 +114875,14 @@ function toWireFrame(f) {
|
|
|
114669
114875
|
dismissed: f.dismissed ?? []
|
|
114670
114876
|
};
|
|
114671
114877
|
}
|
|
114878
|
+
/** The answer on a frame: a track copy asks the ledger, a still carries the
|
|
114879
|
+
* subject the notification froze. */
|
|
114880
|
+
function verdictOfFrame(copy, verdicts) {
|
|
114881
|
+
if (copy.origin === "track") return verdicts.get(copy.trackId);
|
|
114882
|
+
const s = copy.verdictSubject;
|
|
114883
|
+
if (s === void 0) return void 0;
|
|
114884
|
+
return toRetrainVerdict(s);
|
|
114885
|
+
}
|
|
114672
114886
|
/**
|
|
114673
114887
|
* What the operator did with the proposals on a frame (`retrain-review.ts`).
|
|
114674
114888
|
*
|
|
@@ -114729,7 +114943,11 @@ var RetrainService = class {
|
|
|
114729
114943
|
const tracks = [];
|
|
114730
114944
|
for (const deviceId of devices) tracks.push(...await this.deps.tracks.listStaging(deviceId, limit));
|
|
114731
114945
|
const ids = tracks.map((t) => t.trackId);
|
|
114732
|
-
const [frameCounts, annotationCounts] = await Promise.all([
|
|
114946
|
+
const [frameCounts, annotationCounts, verdicts] = await Promise.all([
|
|
114947
|
+
this.deps.frames.countsByTrack(ids),
|
|
114948
|
+
this.deps.annotations.countsByTrack(ids),
|
|
114949
|
+
this.verdictsOfTracks(ids)
|
|
114950
|
+
]);
|
|
114733
114951
|
return tracks.map((t) => ({
|
|
114734
114952
|
trackId: t.trackId,
|
|
114735
114953
|
deviceId: t.deviceId,
|
|
@@ -114738,7 +114956,8 @@ var RetrainService = class {
|
|
|
114738
114956
|
firstSeen: t.firstSeen,
|
|
114739
114957
|
lastSeen: t.lastSeen,
|
|
114740
114958
|
frameCount: frameCounts.get(t.trackId) ?? 0,
|
|
114741
|
-
annotationCount: annotationCounts.get(t.trackId) ?? 0
|
|
114959
|
+
annotationCount: annotationCounts.get(t.trackId) ?? 0,
|
|
114960
|
+
...verdicts.has(t.trackId) ? { verdict: verdicts.get(t.trackId) } : {}
|
|
114742
114961
|
}));
|
|
114743
114962
|
}
|
|
114744
114963
|
/**
|
|
@@ -114788,9 +115007,14 @@ var RetrainService = class {
|
|
|
114788
115007
|
const ownTracks = /* @__PURE__ */ new Map();
|
|
114789
115008
|
const subjectBoxes = /* @__PURE__ */ new Map();
|
|
114790
115009
|
const annotationsByFrame = await this.annotationsByFrame(copies);
|
|
115010
|
+
const verdicts = await this.verdictsOf(copies);
|
|
114791
115011
|
const out = [];
|
|
114792
115012
|
for (const copy of copies) {
|
|
114793
|
-
const
|
|
115013
|
+
const verdict = verdictOfFrame(copy, verdicts);
|
|
115014
|
+
const wire = {
|
|
115015
|
+
...toWireFrame(copy),
|
|
115016
|
+
...verdict !== void 0 ? { verdict } : {}
|
|
115017
|
+
};
|
|
114794
115018
|
const collectionIds = collections?.get(copy.frameId);
|
|
114795
115019
|
const withCollections = {
|
|
114796
115020
|
...collections !== null ? {
|
|
@@ -114821,7 +115045,8 @@ var RetrainService = class {
|
|
|
114821
115045
|
},
|
|
114822
115046
|
ownTrack: trackId !== "" ? ownTracks.get(trackId) ?? null : null,
|
|
114823
115047
|
ownSubjectBox: trackId !== "" ? subjectBoxes.get(trackId)?.get(copy.sourceMediaId) ?? null : null,
|
|
114824
|
-
neighbours
|
|
115048
|
+
neighbours,
|
|
115049
|
+
...copy.verdictSubject !== void 0 ? { verdictSubject: copy.verdictSubject } : {}
|
|
114825
115050
|
});
|
|
114826
115051
|
out.push({
|
|
114827
115052
|
...withCollections,
|
|
@@ -114831,6 +115056,22 @@ var RetrainService = class {
|
|
|
114831
115056
|
}
|
|
114832
115057
|
return out;
|
|
114833
115058
|
}
|
|
115059
|
+
/** The verdicts of the tracks these frames were copied from. */
|
|
115060
|
+
async verdictsOf(copies) {
|
|
115061
|
+
return this.verdictsOfTracks(copies.flatMap((c) => c.origin === "track" ? [c.trackId] : []));
|
|
115062
|
+
}
|
|
115063
|
+
async verdictsOfTracks(trackIds) {
|
|
115064
|
+
const port = this.deps.verdicts;
|
|
115065
|
+
const ids = [...new Set(trackIds)];
|
|
115066
|
+
if (port === void 0 || ids.length === 0) return /* @__PURE__ */ new Map();
|
|
115067
|
+
return port.forTracks(ids).catch((err) => {
|
|
115068
|
+
this.deps.logger.warn("retrain frames: verdicts unreadable", { meta: {
|
|
115069
|
+
tracks: ids.length,
|
|
115070
|
+
error: String(err)
|
|
115071
|
+
} });
|
|
115072
|
+
return /* @__PURE__ */ new Map();
|
|
115073
|
+
});
|
|
115074
|
+
}
|
|
114834
115075
|
/** The URLs of a frame's pixels, when a data plane is served for them. */
|
|
114835
115076
|
frameUrls(frameId) {
|
|
114836
115077
|
const frameUrl = this.deps.frameUrl;
|
|
@@ -116947,6 +117188,7 @@ var BuildStores = class extends BuildStoresBase {
|
|
|
116947
117188
|
subjectBoxes: (trackId, deviceId) => mediaStore.listSubjectBoxesByOwner("track", trackId, deviceId)
|
|
116948
117189
|
},
|
|
116949
117190
|
memberships: retrainCollectionStore,
|
|
117191
|
+
verdicts: { forTracks: async (trackIds) => this.notificationCenter?.detectionVerdictsOf(trackIds) ?? /* @__PURE__ */ new Map() },
|
|
116950
117192
|
frameUrl: (frameId, size) => retrainFramePath(this.ctx.id, frameId, size)
|
|
116951
117193
|
});
|
|
116952
117194
|
this.retrain = retrain;
|
|
@@ -122658,7 +122900,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
122658
122900
|
onBindingsChanged: (data) => this.handleBindingsChangedEvent(data),
|
|
122659
122901
|
onDeviceUnregistered: (deviceId) => this.handleDeviceUnregisteredEvent(deviceId),
|
|
122660
122902
|
onClusterTopology: (nodes) => this.nodeLiveness.applySnapshot(nodes, Date.now()),
|
|
122661
|
-
onSystemEvent: (event) => this.notificationCenter?.onSystemEvent(event)
|
|
122903
|
+
onSystemEvent: (event) => this.notificationCenter?.onSystemEvent(event),
|
|
122904
|
+
onDeviceRegistryChange: () => this.notificationCenter?.invalidateActionTargets()
|
|
122662
122905
|
});
|
|
122663
122906
|
await step("startTrackingTapes", () => this.startTrackingTapes(this.isPostProcessingNode));
|
|
122664
122907
|
if (this.isPostProcessingNode) await step("startEmbeddingDispatcher", () => this.startEmbeddingDispatcher(logger, transport));
|