@camstack/addon-post-analysis 1.2.654 → 1.2.656
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 +14 -0
- package/dist/{clip-model-registry-CFQBxtE2.js → clip-model-registry-BQ_2RsIp.js} +68511 -67387
- package/dist/{clip-model-registry-BX6j2lnF.mjs → clip-model-registry-C06M8A8U.mjs} +69016 -67928
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Vag6rhEh.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-8vah5-KD.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-YXyP9GJm.mjs +26 -0
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DXQUFp7P.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-gv38vKug.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-CnvnZsmk.mjs → hostInit-0O7AUcsV.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +334 -73
- package/dist/pipeline-analytics/index.mjs +333 -72
- 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-BQ_2RsIp.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 {
|
|
@@ -7374,7 +7343,8 @@ var OPENING = new Set([
|
|
|
7374
7343
|
"open",
|
|
7375
7344
|
"opening",
|
|
7376
7345
|
"unlocked",
|
|
7377
|
-
"unlocking"
|
|
7346
|
+
"unlocking",
|
|
7347
|
+
"door-open"
|
|
7378
7348
|
]);
|
|
7379
7349
|
function sensorEdgeOf(kind, value) {
|
|
7380
7350
|
const opening = alarmOpeningFromSensorEvent(kind, value);
|
|
@@ -9289,6 +9259,7 @@ var en_default = {
|
|
|
9289
9259
|
"button.verdict.correct": "Correct",
|
|
9290
9260
|
"button.verdict.wrong": "Wrong",
|
|
9291
9261
|
"button.verdict.wrongAs": "Wrong: it is {{class}}",
|
|
9262
|
+
"button.verdict.wrongLabel": "Wrong label, class OK",
|
|
9292
9263
|
"action.server-update.title": "Server update",
|
|
9293
9264
|
"action.server-update.started": "Updating server on {{nodeId}}…",
|
|
9294
9265
|
"action.server-update.restarting": "Server update staged on {{nodeId}} — the node is restarting. You will hear when it comes back.",
|
|
@@ -9694,6 +9665,7 @@ var it_default = {
|
|
|
9694
9665
|
"button.verdict.correct": "Corretto",
|
|
9695
9666
|
"button.verdict.wrong": "Sbagliato",
|
|
9696
9667
|
"button.verdict.wrongAs": "Sbagliato: è {{class}}",
|
|
9668
|
+
"button.verdict.wrongLabel": "Etichetta sbagliata, classe giusta",
|
|
9697
9669
|
"action.server-update.title": "Aggiornamento server",
|
|
9698
9670
|
"action.server-update.started": "Aggiornamento server su {{nodeId}}…",
|
|
9699
9671
|
"action.server-update.restarting": "Aggiornamento server predisposto su {{nodeId}} — il nodo si riavvia. Ti avviseremo quando torna.",
|
|
@@ -39079,6 +39051,11 @@ function rowIsDurableRetrainCopy(data) {
|
|
|
39079
39051
|
* audit resolves ownership from INDEX rows, but a future filesystem-side tool
|
|
39080
39052
|
* must be able to tell the two corpora apart by path alone. */
|
|
39081
39053
|
var RETRAIN_PATH_PREFIX = "retrain";
|
|
39054
|
+
/** The stored verdict subject, validated; anything unreadable reads as absent. */
|
|
39055
|
+
function narrowVerdictSubject(value) {
|
|
39056
|
+
const parsed = require_clip_model_registry.RetrainVerdictSubjectSchema.safeParse(value);
|
|
39057
|
+
return parsed.success ? { verdictSubject: parsed.data } : {};
|
|
39058
|
+
}
|
|
39082
39059
|
/** Ids per `IN (…)` page in {@link RetrainFrameStore.getMany}. */
|
|
39083
39060
|
var GET_MANY_PAGE = 500;
|
|
39084
39061
|
/**
|
|
@@ -39162,7 +39139,8 @@ var RetrainFrameStore = class {
|
|
|
39162
39139
|
complete: data["complete"] === true || data["complete"] === 1,
|
|
39163
39140
|
...finiteNumber(data["epochMs"]) !== void 0 ? { epochMs: finiteNumber(data["epochMs"]) } : {},
|
|
39164
39141
|
revision: finiteNumber(data["revision"]) ?? 0,
|
|
39165
|
-
dismissed: narrowDismissed(data["dismissed"])
|
|
39142
|
+
dismissed: narrowDismissed(data["dismissed"]),
|
|
39143
|
+
...narrowVerdictSubject(data["verdictSubject"])
|
|
39166
39144
|
};
|
|
39167
39145
|
}
|
|
39168
39146
|
/** The copies with these ids, in the order given; unknown ids are skipped. */
|
|
@@ -39515,7 +39493,8 @@ var RetrainFrameStore = class {
|
|
|
39515
39493
|
capturedAt: input.epochMs,
|
|
39516
39494
|
...chromaSpread !== void 0 ? { chromaSpread } : {},
|
|
39517
39495
|
complete: false,
|
|
39518
|
-
epochMs: input.epochMs
|
|
39496
|
+
epochMs: input.epochMs,
|
|
39497
|
+
...input.verdictSubject !== void 0 ? { verdictSubject: input.verdictSubject } : {}
|
|
39519
39498
|
};
|
|
39520
39499
|
const path = framePath(frame);
|
|
39521
39500
|
const persist = async () => {
|
|
@@ -39539,7 +39518,8 @@ var RetrainFrameStore = class {
|
|
|
39539
39518
|
chromaSpread: frame.chromaSpread ?? null,
|
|
39540
39519
|
complete: false,
|
|
39541
39520
|
origin: "recording",
|
|
39542
|
-
epochMs: input.epochMs
|
|
39521
|
+
epochMs: input.epochMs,
|
|
39522
|
+
...input.verdictSubject !== void 0 ? { verdictSubject: input.verdictSubject } : {}
|
|
39543
39523
|
});
|
|
39544
39524
|
await this.ledger.transact([row, ...withRow]);
|
|
39545
39525
|
};
|
|
@@ -39576,6 +39556,22 @@ var RetrainFrameStore = class {
|
|
|
39576
39556
|
};
|
|
39577
39557
|
}
|
|
39578
39558
|
/**
|
|
39559
|
+
* Record the verdict subject on a still that already existed (the moment was
|
|
39560
|
+
* in the dataset before the operator tapped). The latest answer wins, like
|
|
39561
|
+
* the verdict ledger.
|
|
39562
|
+
*
|
|
39563
|
+
* @returns the updated frame, or `null` when no such copy exists.
|
|
39564
|
+
*/
|
|
39565
|
+
async setVerdictSubject(frameId, verdictSubject) {
|
|
39566
|
+
const existing = await this.get(frameId);
|
|
39567
|
+
if (existing === null) return null;
|
|
39568
|
+
await this.ledger.update(frameId, { verdictSubject });
|
|
39569
|
+
return {
|
|
39570
|
+
...existing,
|
|
39571
|
+
verdictSubject
|
|
39572
|
+
};
|
|
39573
|
+
}
|
|
39574
|
+
/**
|
|
39579
39575
|
* The operator's "every subject in this frame is boxed" (D785 context: an
|
|
39580
39576
|
* unboxed subject is a false positive to a detector evaluation). Only the
|
|
39581
39577
|
* operator sets it; nothing derives it from the annotation count, because a
|
|
@@ -68515,6 +68511,7 @@ function wireSubscriptions(deps) {
|
|
|
68515
68511
|
deps.onClusterTopology(nodes);
|
|
68516
68512
|
}));
|
|
68517
68513
|
for (const category of NC_SYSTEM_EVENT_CATEGORIES) unsubs.push(deps.eventBus.subscribe({ category }, (ev) => deps.onSystemEvent(ev)));
|
|
68514
|
+
for (const category of require_clip_model_registry.ACTION_TARGET_INVALIDATION_EVENTS) unsubs.push(deps.eventBus.subscribe({ category }, () => deps.onDeviceRegistryChange()));
|
|
68518
68515
|
}
|
|
68519
68516
|
unsubs.push(deps.eventBus.subscribe({ category: require_clip_model_registry.EventCategory.DeviceBindingsChanged }, (ev) => {
|
|
68520
68517
|
deps.onBindingsChanged(ev.data);
|
|
@@ -69076,6 +69073,12 @@ async function composeEventKindsForDevices(deviceIds, composeOne, onError) {
|
|
|
69076
69073
|
* nearest one is normally the same detection; the delta is reported so a
|
|
69077
69074
|
* reviewer can see how far it was.
|
|
69078
69075
|
*
|
|
69076
|
+
* 0. **The subject a notification verdict froze** (a recording still landed by
|
|
69077
|
+
* a verdict whose track was already gone). It is the one subject the
|
|
69078
|
+
* operator answered about, so it comes FIRST and is the own box; the
|
|
69079
|
+
* neighbours below stay secondary. Its box is normalised against the full
|
|
69080
|
+
* frame already, so it is used as is — never re-scaled.
|
|
69081
|
+
*
|
|
69079
69082
|
* Every OTHER track alive on the camera at that instant contributes its own
|
|
69080
69083
|
* nearest position the same way — a frame with three people is three boxes,
|
|
69081
69084
|
* and a recording still (no track of its own) gets everything the pipeline
|
|
@@ -69130,6 +69133,18 @@ function toRetrainClassTiers(className, label) {
|
|
|
69130
69133
|
label: kept
|
|
69131
69134
|
} : { macroClass };
|
|
69132
69135
|
}
|
|
69136
|
+
/** A verdict on the wire: the operator's class split into tiers once, here. */
|
|
69137
|
+
function toRetrainVerdict(input) {
|
|
69138
|
+
const tiers = input.suggestedClass !== void 0 ? toRetrainClassTiers(input.suggestedClass, void 0) : null;
|
|
69139
|
+
return {
|
|
69140
|
+
verdict: input.verdict,
|
|
69141
|
+
trackId: input.trackId,
|
|
69142
|
+
...input.suggestedClass !== void 0 ? { suggestedClass: input.suggestedClass } : {},
|
|
69143
|
+
...tiers !== null ? { suggestedMacroClass: tiers.macroClass } : {},
|
|
69144
|
+
...tiers?.label !== void 0 ? { suggestedLabel: tiers.label } : {},
|
|
69145
|
+
...input.at !== void 0 ? { at: input.at } : {}
|
|
69146
|
+
};
|
|
69147
|
+
}
|
|
69133
69148
|
/** A pixel box as a clamped normalised one, or `null` when nothing is left. */
|
|
69134
69149
|
function normalise(box, frameWidth, frameHeight) {
|
|
69135
69150
|
if (frameWidth === void 0 || frameHeight === void 0) return null;
|
|
@@ -69187,6 +69202,22 @@ function boxFor(track, bbox, basis, deltaMs, score) {
|
|
|
69187
69202
|
deltaMs
|
|
69188
69203
|
};
|
|
69189
69204
|
}
|
|
69205
|
+
/** The box a notification verdict froze, or `null` when the event carried none. */
|
|
69206
|
+
function boxOfVerdict(subject) {
|
|
69207
|
+
const box = subject.bbox;
|
|
69208
|
+
if (box === void 0) return null;
|
|
69209
|
+
const clamped = normalise(box, 1, 1);
|
|
69210
|
+
if (clamped === null) return null;
|
|
69211
|
+
const tiers = toRetrainClassTiers(subject.className, subject.label);
|
|
69212
|
+
return {
|
|
69213
|
+
bbox: clamped,
|
|
69214
|
+
macroClass: tiers.macroClass,
|
|
69215
|
+
...tiers.label !== void 0 ? { label: tiers.label } : {},
|
|
69216
|
+
trackId: subject.trackId,
|
|
69217
|
+
basis: "verdict",
|
|
69218
|
+
deltaMs: 0
|
|
69219
|
+
};
|
|
69220
|
+
}
|
|
69190
69221
|
/** The own track's box: the recorded subject box first, the nearest position second. */
|
|
69191
69222
|
function ownBox(input, tolerance) {
|
|
69192
69223
|
const track = input.ownTrack;
|
|
@@ -69209,10 +69240,13 @@ function pipelineBoxesForFrame(input) {
|
|
|
69209
69240
|
const out = [];
|
|
69210
69241
|
const own = ownBox(input, tolerance);
|
|
69211
69242
|
if (own !== null) out.push(own);
|
|
69243
|
+
const verdictBox = input.verdictSubject !== void 0 ? boxOfVerdict(input.verdictSubject) : null;
|
|
69244
|
+
if (verdictBox !== null) out.push(verdictBox);
|
|
69212
69245
|
const at = input.frame.capturedAt;
|
|
69213
69246
|
if (at === void 0) return out;
|
|
69214
69247
|
const ownId = input.frame.trackId;
|
|
69215
69248
|
const seen = new Set(ownId !== void 0 ? [ownId] : []);
|
|
69249
|
+
if (input.verdictSubject !== void 0) seen.add(input.verdictSubject.trackId);
|
|
69216
69250
|
for (const track of input.neighbours) {
|
|
69217
69251
|
if (seen.has(track.trackId) || track.deviceId !== input.frame.deviceId) continue;
|
|
69218
69252
|
seen.add(track.trackId);
|
|
@@ -70534,6 +70568,23 @@ Object.freeze({
|
|
|
70534
70568
|
"sendMediaCommand": { "repeat": 1 },
|
|
70535
70569
|
"setConfig": { "config": { "__nested__": { "defaultTransmitterId": null } } }
|
|
70536
70570
|
},
|
|
70571
|
+
"schedulers": {
|
|
70572
|
+
"conflicts": { "scheduler": { "__nested__": {
|
|
70573
|
+
"enabled": true,
|
|
70574
|
+
"priority": 0,
|
|
70575
|
+
"maxLateMs": 6e5
|
|
70576
|
+
} } },
|
|
70577
|
+
"create": { "scheduler": { "__nested__": {
|
|
70578
|
+
"enabled": true,
|
|
70579
|
+
"priority": 0,
|
|
70580
|
+
"maxLateMs": 6e5
|
|
70581
|
+
} } },
|
|
70582
|
+
"update": { "scheduler": { "__nested__": {
|
|
70583
|
+
"enabled": true,
|
|
70584
|
+
"priority": 0,
|
|
70585
|
+
"maxLateMs": 6e5
|
|
70586
|
+
} } }
|
|
70587
|
+
},
|
|
70537
70588
|
"storage": { "upsertLocation": { "isSystem": false } },
|
|
70538
70589
|
"storage-admin": { "readRecords": { "limit": 100 } },
|
|
70539
70590
|
"storage-provider": {
|
|
@@ -82462,10 +82513,18 @@ function parseActions(raw) {
|
|
|
82462
82513
|
for (const entry of raw) {
|
|
82463
82514
|
const parsed = require_clip_model_registry.NcRuleActionSchema.safeParse(entry);
|
|
82464
82515
|
if (!parsed.success) return null;
|
|
82465
|
-
out.push(parsed.data);
|
|
82516
|
+
out.push(require_clip_model_registry.normalizeNcAction(parsed.data));
|
|
82466
82517
|
}
|
|
82467
82518
|
return out;
|
|
82468
82519
|
}
|
|
82520
|
+
/** Does a stored row still carry a pre-D983 cap step (`deviceId`, no `target`)? */
|
|
82521
|
+
function hasLegacyTarget(raw) {
|
|
82522
|
+
if (!Array.isArray(raw)) return false;
|
|
82523
|
+
return raw.some((entry) => {
|
|
82524
|
+
const parsed = require_clip_model_registry.NcRuleActionSchema.safeParse(entry);
|
|
82525
|
+
return parsed.success && parsed.data.kind === "cap" && parsed.data.target === void 0;
|
|
82526
|
+
});
|
|
82527
|
+
}
|
|
82469
82528
|
function rowToSequence(id, body) {
|
|
82470
82529
|
const actions = parseActions(body.actions);
|
|
82471
82530
|
if (actions === null) return null;
|
|
@@ -82504,6 +82563,7 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82504
82563
|
try {
|
|
82505
82564
|
const { records } = await this.kind.query({ limit: 1e4 });
|
|
82506
82565
|
const next = /* @__PURE__ */ new Map();
|
|
82566
|
+
const legacyRows = [];
|
|
82507
82567
|
let skipped = 0;
|
|
82508
82568
|
for (const row of records) {
|
|
82509
82569
|
const sequence = rowToSequence(row.id, row.data);
|
|
@@ -82512,9 +82572,11 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82512
82572
|
continue;
|
|
82513
82573
|
}
|
|
82514
82574
|
next.set(sequence.id, sequence);
|
|
82575
|
+
if (hasLegacyTarget(row.data.actions)) legacyRows.push(row.id);
|
|
82515
82576
|
}
|
|
82516
82577
|
this.byId.clear();
|
|
82517
82578
|
for (const [id, seq] of next) this.byId.set(id, seq);
|
|
82579
|
+
await this.rewriteLegacyRows(legacyRows, records);
|
|
82518
82580
|
if (skipped > 0) this.logger.warn("sequence rows skipped as malformed — bindings to them will miss", { meta: { skipped } });
|
|
82519
82581
|
} catch (err) {
|
|
82520
82582
|
this.logger.warn("sequence load failed — keeping the sequences already in memory", { meta: {
|
|
@@ -82523,6 +82585,28 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82523
82585
|
} });
|
|
82524
82586
|
}
|
|
82525
82587
|
}
|
|
82588
|
+
/**
|
|
82589
|
+
* One-shot, idempotent (D983): a row whose steps still carry `deviceId` is
|
|
82590
|
+
* stored again in the target shape. A failed write leaves the row as it was —
|
|
82591
|
+
* it is still READ as the new shape — and is on the record.
|
|
82592
|
+
*/
|
|
82593
|
+
async rewriteLegacyRows(ids, records) {
|
|
82594
|
+
for (const id of ids) {
|
|
82595
|
+
const sequence = this.byId.get(id) ?? null;
|
|
82596
|
+
const row = records.find((r) => r.id === id);
|
|
82597
|
+
if (sequence === null || row === void 0) continue;
|
|
82598
|
+
try {
|
|
82599
|
+
const createdAt = row.data.createdAt;
|
|
82600
|
+
await this.persist(sequence, typeof createdAt === "number" && Number.isFinite(createdAt) ? createdAt : this.now(), this.now());
|
|
82601
|
+
this.logger.info("notification sequence rewritten to the target shape", { meta: { sequenceId: id } });
|
|
82602
|
+
} catch (err) {
|
|
82603
|
+
this.logger.warn("notification sequence could not be rewritten — it is still read as the target shape", { meta: {
|
|
82604
|
+
sequenceId: id,
|
|
82605
|
+
error: String(err)
|
|
82606
|
+
} });
|
|
82607
|
+
}
|
|
82608
|
+
}
|
|
82609
|
+
}
|
|
82526
82610
|
list() {
|
|
82527
82611
|
return [...this.byId.values()].toSorted((a, b) => a.name.localeCompare(b.name));
|
|
82528
82612
|
}
|
|
@@ -82542,7 +82626,7 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82542
82626
|
id: (0, node_crypto.randomUUID)(),
|
|
82543
82627
|
name: input.name,
|
|
82544
82628
|
...input.minDelaySec !== void 0 ? { minDelaySec: input.minDelaySec } : {},
|
|
82545
|
-
actions:
|
|
82629
|
+
actions: input.actions.map(require_clip_model_registry.normalizeNcAction)
|
|
82546
82630
|
};
|
|
82547
82631
|
await this.persist(sequence, now, now);
|
|
82548
82632
|
this.byId.set(sequence.id, sequence);
|
|
@@ -82558,7 +82642,7 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82558
82642
|
id: sequence.id,
|
|
82559
82643
|
name: sequence.name,
|
|
82560
82644
|
...sequence.minDelaySec !== void 0 ? { minDelaySec: sequence.minDelaySec } : {},
|
|
82561
|
-
actions:
|
|
82645
|
+
actions: sequence.actions.map(require_clip_model_registry.normalizeNcAction)
|
|
82562
82646
|
};
|
|
82563
82647
|
await this.persist(stored, now, now);
|
|
82564
82648
|
this.byId.set(stored.id, stored);
|
|
@@ -82571,7 +82655,7 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82571
82655
|
id: sequenceId,
|
|
82572
82656
|
name: input.name,
|
|
82573
82657
|
...input.minDelaySec !== void 0 ? { minDelaySec: input.minDelaySec } : {},
|
|
82574
|
-
actions:
|
|
82658
|
+
actions: input.actions.map(require_clip_model_registry.normalizeNcAction)
|
|
82575
82659
|
};
|
|
82576
82660
|
const createdAt = await this.readCreatedAt(sequenceId, now);
|
|
82577
82661
|
await this.persist(sequence, createdAt, now);
|
|
@@ -82639,7 +82723,11 @@ var NC_DETECTION_VERDICTS_COLUMNS = [
|
|
|
82639
82723
|
var NcDetectionVerdictRecordSchema = require_clip_model_registry.object({
|
|
82640
82724
|
trackId: require_clip_model_registry.string().min(1),
|
|
82641
82725
|
deviceId: require_clip_model_registry.number().int(),
|
|
82642
|
-
verdict: require_clip_model_registry._enum([
|
|
82726
|
+
verdict: require_clip_model_registry._enum([
|
|
82727
|
+
"correct",
|
|
82728
|
+
"wrong",
|
|
82729
|
+
"wrong-label"
|
|
82730
|
+
]),
|
|
82643
82731
|
/** The class the operator said it really was. Only with `wrong`. */
|
|
82644
82732
|
suggestedClass: require_clip_model_registry.string().min(1).optional(),
|
|
82645
82733
|
/** Whose notification it was (the token's frozen owner), NOT who tapped. */
|
|
@@ -82674,6 +82762,16 @@ var NcDetectionVerdictLedger = class {
|
|
|
82674
82762
|
logger: deps.logger
|
|
82675
82763
|
});
|
|
82676
82764
|
}
|
|
82765
|
+
/** The answers recorded for these tracks (a track nobody answered is absent).
|
|
82766
|
+
* Reads the in-memory mirror: no I/O, so a listing never waits on it. */
|
|
82767
|
+
forTracks(trackIds) {
|
|
82768
|
+
const out = /* @__PURE__ */ new Map();
|
|
82769
|
+
for (const id of trackIds) {
|
|
82770
|
+
const row = this.ledger.get(id);
|
|
82771
|
+
if (row !== void 0) out.set(id, row);
|
|
82772
|
+
}
|
|
82773
|
+
return out;
|
|
82774
|
+
}
|
|
82677
82775
|
/** Write-through; throws on a failed write so the tap reports it. */
|
|
82678
82776
|
async record(row) {
|
|
82679
82777
|
await this.ledger.put(NcDetectionVerdictRecordSchema.parse(row));
|
|
@@ -82807,10 +82905,33 @@ function pruneHookBindings(bindings, field, isDead, notes) {
|
|
|
82807
82905
|
kept.push(binding);
|
|
82808
82906
|
continue;
|
|
82809
82907
|
}
|
|
82810
|
-
if (binding.action.kind === "cap"
|
|
82811
|
-
|
|
82812
|
-
|
|
82813
|
-
|
|
82908
|
+
if (binding.action.kind === "cap") {
|
|
82909
|
+
const { target } = require_clip_model_registry.normalizeNcCapTarget(binding.action);
|
|
82910
|
+
if (target.kind === "devices") {
|
|
82911
|
+
const dead = target.ids.filter(isDead);
|
|
82912
|
+
if (dead.length === target.ids.length) {
|
|
82913
|
+
changed = true;
|
|
82914
|
+
notes.removed(field, deviceSubjects(dead), "binding-removed");
|
|
82915
|
+
continue;
|
|
82916
|
+
}
|
|
82917
|
+
if (dead.length > 0) {
|
|
82918
|
+
changed = true;
|
|
82919
|
+
notes.removed(field, deviceSubjects(dead), "binding-removed");
|
|
82920
|
+
const alive = target.ids.filter((id) => !isDead(id));
|
|
82921
|
+
kept.push({
|
|
82922
|
+
...binding,
|
|
82923
|
+
action: require_clip_model_registry.normalizeNcAction({
|
|
82924
|
+
...binding.action,
|
|
82925
|
+
deviceId: void 0,
|
|
82926
|
+
target: {
|
|
82927
|
+
kind: "devices",
|
|
82928
|
+
ids: alive
|
|
82929
|
+
}
|
|
82930
|
+
})
|
|
82931
|
+
});
|
|
82932
|
+
continue;
|
|
82933
|
+
}
|
|
82934
|
+
}
|
|
82814
82935
|
}
|
|
82815
82936
|
kept.push(binding);
|
|
82816
82937
|
}
|
|
@@ -82966,7 +83087,11 @@ function referencedDeviceIds(snapshot) {
|
|
|
82966
83087
|
add(c.systemEvent?.deviceIds);
|
|
82967
83088
|
if (c.deviceState !== void 0) ids.add(c.deviceState.deviceId);
|
|
82968
83089
|
if (c.scene?.deviceId !== void 0) ids.add(c.scene.deviceId);
|
|
82969
|
-
for (const binding of [...rule.actions?.onTrigger ?? [], ...rule.actions?.onTriggerOff ?? []])
|
|
83090
|
+
for (const binding of [...rule.actions?.onTrigger ?? [], ...rule.actions?.onTriggerOff ?? []]) {
|
|
83091
|
+
if (binding.kind !== "do" || binding.action.kind !== "cap") continue;
|
|
83092
|
+
const { target } = require_clip_model_registry.normalizeNcCapTarget(binding.action);
|
|
83093
|
+
if (target.kind === "devices") add(target.ids);
|
|
83094
|
+
}
|
|
82970
83095
|
}
|
|
82971
83096
|
for (const rule of snapshot.timelapse) add(rule.deviceIds);
|
|
82972
83097
|
for (const rule of snapshot.summary) add(rule.deviceIds);
|
|
@@ -83637,6 +83762,8 @@ var NcDeviceStateCache = class {
|
|
|
83637
83762
|
//#region src/notification-center/detection-verdict-buttons.ts
|
|
83638
83763
|
var VERDICT_CORRECT_ID = "detection-verdict-correct";
|
|
83639
83764
|
var VERDICT_WRONG_ID = "detection-verdict-wrong";
|
|
83765
|
+
/** Wrong LABEL, right class — only for a subject that has a label. */
|
|
83766
|
+
var VERDICT_WRONG_LABEL_ID = "detection-verdict-wrong-label";
|
|
83640
83767
|
/** `detection-verdict-wrong:<class>` — Wrong, and what it really was. */
|
|
83641
83768
|
var VERDICT_WRONG_AS_PREFIX = "detection-verdict-wrong:";
|
|
83642
83769
|
/** The macro classes offered as "it was really…". Short on purpose. */
|
|
@@ -83653,6 +83780,15 @@ function verdictButtonRefusal(subject) {
|
|
|
83653
83780
|
if (subject.ownerUserId === void 0) return "unowned-target";
|
|
83654
83781
|
return null;
|
|
83655
83782
|
}
|
|
83783
|
+
/**
|
|
83784
|
+
* Does the notified subject carry a label below its macro class — a sub-class
|
|
83785
|
+
* (`dog` under `animal`), a recognised name, a plate? The same tier split the
|
|
83786
|
+
* retrain annotations use, so "has a label" means one thing in both places.
|
|
83787
|
+
*/
|
|
83788
|
+
function subjectHasLabel(className, label) {
|
|
83789
|
+
if (className === void 0) return false;
|
|
83790
|
+
return toRetrainClassTiers(className, label).label !== void 0;
|
|
83791
|
+
}
|
|
83656
83792
|
/** The classes worth offering for a subject of `className`. */
|
|
83657
83793
|
function verdictClassChoices(className) {
|
|
83658
83794
|
return VERDICT_CLASS_CHOICES.filter((c) => c !== className).slice(0, VERDICT_MAX_CLASS_CHOICES);
|
|
@@ -83664,13 +83800,17 @@ function verdictClassChoices(className) {
|
|
|
83664
83800
|
* when the channel declared room for a fourth button.
|
|
83665
83801
|
*/
|
|
83666
83802
|
function verdictButtonSpecs(input) {
|
|
83667
|
-
const
|
|
83803
|
+
const plain = [{
|
|
83668
83804
|
actionId: VERDICT_CORRECT_ID,
|
|
83669
83805
|
verdict: "correct"
|
|
83670
83806
|
}, {
|
|
83671
83807
|
actionId: VERDICT_WRONG_ID,
|
|
83672
83808
|
verdict: "wrong"
|
|
83673
83809
|
}];
|
|
83810
|
+
const base = subjectHasLabel(input.className, input.label) ? [...plain, {
|
|
83811
|
+
actionId: VERDICT_WRONG_LABEL_ID,
|
|
83812
|
+
verdict: "wrong-label"
|
|
83813
|
+
}] : plain;
|
|
83674
83814
|
if (input.maxActions === void 0 || input.maxActions <= 3) return base;
|
|
83675
83815
|
const room = input.maxActions - base.length;
|
|
83676
83816
|
const extra = verdictClassChoices(input.className).slice(0, room).map((suggestedClass) => ({
|
|
@@ -87401,6 +87541,8 @@ var NcDispatcher = class {
|
|
|
87401
87541
|
...target.ownerUserId !== void 0 ? { ownerUserId: target.ownerUserId } : {},
|
|
87402
87542
|
...entry.payload.subject.className !== void 0 ? { className: entry.payload.subject.className } : {},
|
|
87403
87543
|
...entry.payload.subject.trackId !== void 0 ? { trackId: entry.payload.subject.trackId } : {},
|
|
87544
|
+
...entry.payload.subject.label !== void 0 ? { label: entry.payload.subject.label } : {},
|
|
87545
|
+
...entry.payload.subject.bbox !== void 0 ? { bbox: entry.payload.subject.bbox } : {},
|
|
87404
87546
|
eventAtMs: entry.payload.subject.timestamp,
|
|
87405
87547
|
...target.maxActions !== void 0 ? { maxActions: target.maxActions } : {},
|
|
87406
87548
|
...entry.payload.subject.systemEvent !== void 0 ? { systemEvent: entry.payload.subject.systemEvent } : {}
|
|
@@ -91764,7 +91906,7 @@ var NcRuleActionRunner = class {
|
|
|
91764
91906
|
if (sequence.throttle) this.lastRunAt.set(sequence.id, now);
|
|
91765
91907
|
let stepsRun = 0;
|
|
91766
91908
|
for (const [index, action] of sequence.actions.entries()) try {
|
|
91767
|
-
await this.step(action, input.origin);
|
|
91909
|
+
await this.step(action, input.origin, sequence.id);
|
|
91768
91910
|
stepsRun += 1;
|
|
91769
91911
|
} catch (err) {
|
|
91770
91912
|
this.deps.logger.warn("rule action failed — stopping the sequence", {
|
|
@@ -91804,20 +91946,28 @@ var NcRuleActionRunner = class {
|
|
|
91804
91946
|
stepsRun
|
|
91805
91947
|
};
|
|
91806
91948
|
}
|
|
91807
|
-
async step(action, origin) {
|
|
91949
|
+
async step(action, origin, owner) {
|
|
91808
91950
|
if (action.kind === "wait") {
|
|
91809
91951
|
await this.sleep(action.seconds * 1e3);
|
|
91810
91952
|
return;
|
|
91811
91953
|
}
|
|
91812
91954
|
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
|
-
|
|
91955
|
+
const { target, actionId } = require_clip_model_registry.normalizeNcCapTarget(action);
|
|
91956
|
+
const deviceIds = await this.devicesOf(target, actionId, owner);
|
|
91957
|
+
for (const deviceId of deviceIds) await this.deps.actuate({
|
|
91958
|
+
deviceId,
|
|
91815
91959
|
cap: action.cap,
|
|
91816
91960
|
method: action.method,
|
|
91817
91961
|
args: action.args ?? {},
|
|
91818
91962
|
...origin !== void 0 ? { origin } : {}
|
|
91819
91963
|
});
|
|
91820
91964
|
}
|
|
91965
|
+
async devicesOf(target, actionId, owner) {
|
|
91966
|
+
if (target.kind === "devices") return target.ids;
|
|
91967
|
+
if (target.kind === "var") throw new Error("rule action refused: a sequence has no variables to resolve a target from");
|
|
91968
|
+
if (this.deps.resolveTarget === void 0) throw new Error("rule action refused: this node cannot resolve a domain target");
|
|
91969
|
+
return (await this.deps.resolveTarget(target, actionId, owner)).deviceIds;
|
|
91970
|
+
}
|
|
91821
91971
|
};
|
|
91822
91972
|
/** Force-enable a shared sequence for a button tap (always runs, still throttled). */
|
|
91823
91973
|
function asForcedSequence(sequence) {
|
|
@@ -100205,6 +100355,12 @@ function rowRefusalId(systemEvent) {
|
|
|
100205
100355
|
const value = systemEvent?.data["refusalId"];
|
|
100206
100356
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
100207
100357
|
}
|
|
100358
|
+
/** The text-catalog key of each verdict button. */
|
|
100359
|
+
var VERDICT_LABEL_KEY = {
|
|
100360
|
+
correct: "button.verdict.correct",
|
|
100361
|
+
wrong: "button.verdict.wrong",
|
|
100362
|
+
"wrong-label": "button.verdict.wrongLabel"
|
|
100363
|
+
};
|
|
100208
100364
|
/**
|
|
100209
100365
|
* The tap-through buttons for ONE delivery, each with its own single-use
|
|
100210
100366
|
* token.
|
|
@@ -100241,6 +100397,7 @@ async function mintButtons(host, input) {
|
|
|
100241
100397
|
const verdictOwner = input.ownerUserId;
|
|
100242
100398
|
const verdictButtons = verdictRefusal !== null || verdictTrackId === void 0 || verdictOwner === void 0 ? [] : verdictButtonSpecs({
|
|
100243
100399
|
className: input.className,
|
|
100400
|
+
label: input.label,
|
|
100244
100401
|
maxActions: input.maxActions
|
|
100245
100402
|
}).map((spec) => {
|
|
100246
100403
|
const id = (0, node_crypto.randomUUID)();
|
|
@@ -100258,6 +100415,11 @@ async function mintButtons(host, input) {
|
|
|
100258
100415
|
...spec.suggestedClass !== void 0 ? { suggestedClass: spec.suggestedClass } : {},
|
|
100259
100416
|
...input.maxActions === 0 ? { viaLink: true } : {},
|
|
100260
100417
|
...input.eventAtMs !== void 0 ? { eventAtMs: input.eventAtMs } : {},
|
|
100418
|
+
...input.className !== void 0 ? { subject: {
|
|
100419
|
+
className: input.className,
|
|
100420
|
+
...input.label !== void 0 ? { label: input.label } : {},
|
|
100421
|
+
...input.bbox !== void 0 ? { bbox: input.bbox } : {}
|
|
100422
|
+
} } : {},
|
|
100261
100423
|
deviceId: input.deviceId,
|
|
100262
100424
|
actionId: spec.actionId,
|
|
100263
100425
|
targetId: input.targetId,
|
|
@@ -100266,7 +100428,7 @@ async function mintButtons(host, input) {
|
|
|
100266
100428
|
});
|
|
100267
100429
|
return {
|
|
100268
100430
|
id: spec.actionId,
|
|
100269
|
-
label: spec.suggestedClass === void 0 ? host.texts.text({ key: spec.verdict
|
|
100431
|
+
label: spec.suggestedClass === void 0 ? host.texts.text({ key: VERDICT_LABEL_KEY[spec.verdict] }) : host.texts.text({
|
|
100270
100432
|
key: "button.verdict.wrongAs",
|
|
100271
100433
|
vars: { class: spec.suggestedClass }
|
|
100272
100434
|
}),
|
|
@@ -100725,6 +100887,18 @@ var VERDICT_FRAME_REFUSAL_COPY = {
|
|
|
100725
100887
|
"still-busy": "The track is gone and the recorder is busy — nothing was changed, try again later",
|
|
100726
100888
|
failed: "The track is gone and its frame could not be saved — nothing was changed"
|
|
100727
100889
|
};
|
|
100890
|
+
/** The grant's frozen subject, in the shape the retrain still stores it in. */
|
|
100891
|
+
function verdictSubjectOf(grant, subject, at) {
|
|
100892
|
+
return {
|
|
100893
|
+
trackId: grant.trackId,
|
|
100894
|
+
className: subject.className,
|
|
100895
|
+
...subject.label !== void 0 ? { label: subject.label } : {},
|
|
100896
|
+
...subject.bbox !== void 0 ? { bbox: subject.bbox } : {},
|
|
100897
|
+
verdict: grant.verdict,
|
|
100898
|
+
...grant.suggestedClass !== void 0 ? { suggestedClass: grant.suggestedClass } : {},
|
|
100899
|
+
at
|
|
100900
|
+
};
|
|
100901
|
+
}
|
|
100728
100902
|
/**
|
|
100729
100903
|
* The track is gone (aged out, or discarded as a still object): the camera's
|
|
100730
100904
|
* RECORDING still covers the instant, so the verdict lands a frame there
|
|
@@ -100747,7 +100921,8 @@ async function landFrameFromRecording(host, grant) {
|
|
|
100747
100921
|
}
|
|
100748
100922
|
const landed = await addFrame({
|
|
100749
100923
|
deviceId: grant.deviceId,
|
|
100750
|
-
epochMs: grant.eventAtMs
|
|
100924
|
+
epochMs: grant.eventAtMs,
|
|
100925
|
+
...grant.subject !== void 0 ? { subject: verdictSubjectOf(grant, grant.subject, host.now()) } : {}
|
|
100751
100926
|
});
|
|
100752
100927
|
if (!landed.ok) {
|
|
100753
100928
|
host.logger.warn("detection verdict dropped — the track is gone and the recording refused", {
|
|
@@ -101708,6 +101883,7 @@ function buildEntries(host, rule, subject, kind, matchedOn, userTargets = [], or
|
|
|
101708
101883
|
...alarmSource?.eventId !== void 0 ? { eventId: alarmSource.eventId } : hasEventMedia ? { eventId: subject.recordId } : {},
|
|
101709
101884
|
className: subject.classNames[0] ?? subject.sensorKind ?? "event",
|
|
101710
101885
|
...subject.label !== void 0 ? { label: subject.label } : {},
|
|
101886
|
+
...subject.bbox !== void 0 ? { bbox: subject.bbox } : {},
|
|
101711
101887
|
...subject.labelKind !== void 0 ? { labelKind: subject.labelKind } : {},
|
|
101712
101888
|
...subject.confidence !== void 0 ? { confidence: subject.confidence } : {},
|
|
101713
101889
|
zones: renderedZones,
|
|
@@ -106028,6 +106204,8 @@ var NotificationCenter = class {
|
|
|
106028
106204
|
zoneOwners;
|
|
106029
106205
|
/** Runs a matched rule's action sequences. Null when no actuator is wired. */
|
|
106030
106206
|
actionRunner;
|
|
106207
|
+
/** Domain action targets read from the registry (D983); null without a directory. */
|
|
106208
|
+
actionTargets;
|
|
106031
106209
|
/** Grants behind the tap-through buttons. In-process and single-use — read
|
|
106032
106210
|
* `action-token.ts` before assuming anything about what a tap proves. */
|
|
106033
106211
|
actionTokens = new NcActionTokenRegistry();
|
|
@@ -106435,9 +106613,15 @@ var NotificationCenter = class {
|
|
|
106435
106613
|
});
|
|
106436
106614
|
this.livenessHoldoff = new DeviceLivenessHoldoff((event) => this.consumeEvent(event), { delayMs: deps.livenessHoldoffMs ?? (process.env.VITEST ? 0 : DEVICE_LIVENESS_HOLDOFF_MS) });
|
|
106437
106615
|
this.nodeLivenessHoldoff = new DeviceLivenessHoldoff((event, timing) => this.consumeNodeLiveness(event, timing), { delayMs: deps.nodeLivenessHoldoffMs ?? (process.env.VITEST ? 0 : NODE_LIVENESS_HOLDOFF_MS) });
|
|
106616
|
+
const targets = deps.actionTargetDirectory !== void 0 ? new require_clip_model_registry.ActionTargetResolver(deps.actionTargetDirectory, this.logger.child("actions")) : null;
|
|
106617
|
+
this.actionTargets = targets;
|
|
106438
106618
|
this.actionRunner = deps.actuate !== void 0 && deps.isCapDeviceScoped !== void 0 ? new NcRuleActionRunner({
|
|
106439
106619
|
logger: this.logger.child("actions"),
|
|
106440
106620
|
actuate: (input) => this.actuateWithAttribution(input),
|
|
106621
|
+
...targets !== null ? { resolveTarget: (target, actionId, owner) => targets.resolve(target, actionId, {}, {
|
|
106622
|
+
actionId,
|
|
106623
|
+
owner
|
|
106624
|
+
}) } : {},
|
|
106441
106625
|
isDeviceScoped: deps.isCapDeviceScoped,
|
|
106442
106626
|
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
106443
106627
|
}) : null;
|
|
@@ -107227,6 +107411,21 @@ var NotificationCenter = class {
|
|
|
107227
107411
|
}
|
|
107228
107412
|
};
|
|
107229
107413
|
}
|
|
107414
|
+
/**
|
|
107415
|
+
* What the operator answered to the "was this detection right?" buttons, for
|
|
107416
|
+
* the retrain page: by track, the latest answer wins. A track nobody answered
|
|
107417
|
+
* is absent.
|
|
107418
|
+
*/
|
|
107419
|
+
detectionVerdictsOf(trackIds) {
|
|
107420
|
+
const out = /* @__PURE__ */ new Map();
|
|
107421
|
+
for (const [trackId, row] of this.verdicts.forTracks(trackIds)) out.set(trackId, toRetrainVerdict({
|
|
107422
|
+
verdict: row.verdict,
|
|
107423
|
+
trackId,
|
|
107424
|
+
at: row.at,
|
|
107425
|
+
suggestedClass: row.suggestedClass
|
|
107426
|
+
}));
|
|
107427
|
+
return out;
|
|
107428
|
+
}
|
|
107230
107429
|
observeOccupancy(deviceId, snapshot, evidence = "scene") {
|
|
107231
107430
|
return observeOccupancy(this.occupancyWatchHost(), deviceId, snapshot, evidence);
|
|
107232
107431
|
}
|
|
@@ -107402,6 +107601,10 @@ var NotificationCenter = class {
|
|
|
107402
107601
|
}
|
|
107403
107602
|
};
|
|
107404
107603
|
}
|
|
107604
|
+
/** The device registry changed: a cached domain action target is stale (D983). */
|
|
107605
|
+
invalidateActionTargets() {
|
|
107606
|
+
this.actionTargets?.invalidate();
|
|
107607
|
+
}
|
|
107405
107608
|
onSystemEvent(event) {
|
|
107406
107609
|
return onSystemEvent(this.systemEventsHost(), event);
|
|
107407
107610
|
}
|
|
@@ -108875,7 +109078,9 @@ async function addVerdictFrameFromRecording(api, logger, input) {
|
|
|
108875
109078
|
}
|
|
108876
109079
|
const result = await api.addRetrainFrameFromRecording({
|
|
108877
109080
|
collectionId,
|
|
108878
|
-
|
|
109081
|
+
deviceId: input.deviceId,
|
|
109082
|
+
epochMs: input.epochMs,
|
|
109083
|
+
...input.subject !== void 0 ? { verdictSubject: input.subject } : {}
|
|
108879
109084
|
});
|
|
108880
109085
|
if (result.kind === "added") return { ok: true };
|
|
108881
109086
|
return {
|
|
@@ -109162,6 +109367,20 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
|
|
|
109162
109367
|
});
|
|
109163
109368
|
},
|
|
109164
109369
|
isCapDeviceScoped: require_clip_model_registry.isDeviceScopedCap,
|
|
109370
|
+
actionTargetDirectory: {
|
|
109371
|
+
listDeviceIdsByCaps: (capNames) => api.deviceManager.listDeviceIdsByCaps.query({ capNames: [...capNames] }),
|
|
109372
|
+
listDevices: async (deviceIds) => {
|
|
109373
|
+
return (await api.deviceManager.listAll.query({
|
|
109374
|
+
deviceIds: [...deviceIds],
|
|
109375
|
+
projection: "slim"
|
|
109376
|
+
})).map((d) => ({
|
|
109377
|
+
id: d.id,
|
|
109378
|
+
type: d.type,
|
|
109379
|
+
location: d.location,
|
|
109380
|
+
disabled: d.disabled
|
|
109381
|
+
}));
|
|
109382
|
+
}
|
|
109383
|
+
},
|
|
109165
109384
|
generateVision: async (request) => {
|
|
109166
109385
|
const result = await api.llm.generateVision.mutate({
|
|
109167
109386
|
...request.profileId !== void 0 ? { profileId: request.profileId } : {},
|
|
@@ -113787,7 +114006,7 @@ var RetrainCollectionService = class {
|
|
|
113787
114006
|
frameId: recordingFrameId(input.deviceId, input.epochMs),
|
|
113788
114007
|
deviceId: input.deviceId
|
|
113789
114008
|
});
|
|
113790
|
-
const stored = await this.storeStill(input.deviceId, input.epochMs, membershipOp === null ? [] : [membershipOp]);
|
|
114009
|
+
const stored = await this.storeStill(input.deviceId, input.epochMs, membershipOp === null ? [] : [membershipOp], input.verdictSubject);
|
|
113791
114010
|
if (stored.kind === "refused") {
|
|
113792
114011
|
this.deps.logger.info("retrain collection add refused", {
|
|
113793
114012
|
tags,
|
|
@@ -113799,11 +114018,15 @@ var RetrainCollectionService = class {
|
|
|
113799
114018
|
});
|
|
113800
114019
|
return stored;
|
|
113801
114020
|
}
|
|
113802
|
-
|
|
113803
|
-
|
|
113804
|
-
|
|
113805
|
-
|
|
113806
|
-
|
|
114021
|
+
let storedFrame = stored.frame;
|
|
114022
|
+
if (stored.reused) {
|
|
114023
|
+
await this.deps.collections.addFrame({
|
|
114024
|
+
collectionId: input.collectionId,
|
|
114025
|
+
frameId: stored.frame.frameId,
|
|
114026
|
+
deviceId: input.deviceId
|
|
114027
|
+
});
|
|
114028
|
+
if (input.verdictSubject !== void 0) storedFrame = await this.deps.frames.setVerdictSubject(stored.frame.frameId, input.verdictSubject) ?? stored.frame;
|
|
114029
|
+
}
|
|
113807
114030
|
this.deps.logger.info("retrain frame added to a collection from the recording", {
|
|
113808
114031
|
tags,
|
|
113809
114032
|
meta: {
|
|
@@ -113813,7 +114036,7 @@ var RetrainCollectionService = class {
|
|
|
113813
114036
|
reused: stored.reused
|
|
113814
114037
|
}
|
|
113815
114038
|
});
|
|
113816
|
-
const [wire] = await this.deps.retrain.toWireFrames([
|
|
114039
|
+
const [wire] = await this.deps.retrain.toWireFrames([storedFrame]);
|
|
113817
114040
|
if (wire === void 0) throw new Error("retrain: a stored still produced no wire frame");
|
|
113818
114041
|
return {
|
|
113819
114042
|
kind: "added",
|
|
@@ -113864,7 +114087,7 @@ var RetrainCollectionService = class {
|
|
|
113864
114087
|
const ids = await this.deps.collections.listFrameIds(input.collectionId);
|
|
113865
114088
|
return this.deps.retrain.toWireFrames(await this.deps.frames.getMany(ids));
|
|
113866
114089
|
}
|
|
113867
|
-
async storeStill(deviceId, epochMs, withRow) {
|
|
114090
|
+
async storeStill(deviceId, epochMs, withRow, verdictSubject) {
|
|
113868
114091
|
const existing = await this.deps.frames.get(recordingFrameId(deviceId, epochMs));
|
|
113869
114092
|
if (existing !== null) return {
|
|
113870
114093
|
kind: "stored",
|
|
@@ -113883,7 +114106,8 @@ var RetrainCollectionService = class {
|
|
|
113883
114106
|
const stored = await this.deps.frames.storeRecordingStill({
|
|
113884
114107
|
deviceId,
|
|
113885
114108
|
epochMs,
|
|
113886
|
-
bytes: still.jpeg
|
|
114109
|
+
bytes: still.jpeg,
|
|
114110
|
+
...verdictSubject !== void 0 ? { verdictSubject } : {}
|
|
113887
114111
|
}, withRow);
|
|
113888
114112
|
return stored.kind === "copied" ? {
|
|
113889
114113
|
kind: "stored",
|
|
@@ -114669,6 +114893,14 @@ function toWireFrame(f) {
|
|
|
114669
114893
|
dismissed: f.dismissed ?? []
|
|
114670
114894
|
};
|
|
114671
114895
|
}
|
|
114896
|
+
/** The answer on a frame: a track copy asks the ledger, a still carries the
|
|
114897
|
+
* subject the notification froze. */
|
|
114898
|
+
function verdictOfFrame(copy, verdicts) {
|
|
114899
|
+
if (copy.origin === "track") return verdicts.get(copy.trackId);
|
|
114900
|
+
const s = copy.verdictSubject;
|
|
114901
|
+
if (s === void 0) return void 0;
|
|
114902
|
+
return toRetrainVerdict(s);
|
|
114903
|
+
}
|
|
114672
114904
|
/**
|
|
114673
114905
|
* What the operator did with the proposals on a frame (`retrain-review.ts`).
|
|
114674
114906
|
*
|
|
@@ -114729,7 +114961,11 @@ var RetrainService = class {
|
|
|
114729
114961
|
const tracks = [];
|
|
114730
114962
|
for (const deviceId of devices) tracks.push(...await this.deps.tracks.listStaging(deviceId, limit));
|
|
114731
114963
|
const ids = tracks.map((t) => t.trackId);
|
|
114732
|
-
const [frameCounts, annotationCounts] = await Promise.all([
|
|
114964
|
+
const [frameCounts, annotationCounts, verdicts] = await Promise.all([
|
|
114965
|
+
this.deps.frames.countsByTrack(ids),
|
|
114966
|
+
this.deps.annotations.countsByTrack(ids),
|
|
114967
|
+
this.verdictsOfTracks(ids)
|
|
114968
|
+
]);
|
|
114733
114969
|
return tracks.map((t) => ({
|
|
114734
114970
|
trackId: t.trackId,
|
|
114735
114971
|
deviceId: t.deviceId,
|
|
@@ -114738,7 +114974,8 @@ var RetrainService = class {
|
|
|
114738
114974
|
firstSeen: t.firstSeen,
|
|
114739
114975
|
lastSeen: t.lastSeen,
|
|
114740
114976
|
frameCount: frameCounts.get(t.trackId) ?? 0,
|
|
114741
|
-
annotationCount: annotationCounts.get(t.trackId) ?? 0
|
|
114977
|
+
annotationCount: annotationCounts.get(t.trackId) ?? 0,
|
|
114978
|
+
...verdicts.has(t.trackId) ? { verdict: verdicts.get(t.trackId) } : {}
|
|
114742
114979
|
}));
|
|
114743
114980
|
}
|
|
114744
114981
|
/**
|
|
@@ -114788,9 +115025,14 @@ var RetrainService = class {
|
|
|
114788
115025
|
const ownTracks = /* @__PURE__ */ new Map();
|
|
114789
115026
|
const subjectBoxes = /* @__PURE__ */ new Map();
|
|
114790
115027
|
const annotationsByFrame = await this.annotationsByFrame(copies);
|
|
115028
|
+
const verdicts = await this.verdictsOf(copies);
|
|
114791
115029
|
const out = [];
|
|
114792
115030
|
for (const copy of copies) {
|
|
114793
|
-
const
|
|
115031
|
+
const verdict = verdictOfFrame(copy, verdicts);
|
|
115032
|
+
const wire = {
|
|
115033
|
+
...toWireFrame(copy),
|
|
115034
|
+
...verdict !== void 0 ? { verdict } : {}
|
|
115035
|
+
};
|
|
114794
115036
|
const collectionIds = collections?.get(copy.frameId);
|
|
114795
115037
|
const withCollections = {
|
|
114796
115038
|
...collections !== null ? {
|
|
@@ -114821,7 +115063,8 @@ var RetrainService = class {
|
|
|
114821
115063
|
},
|
|
114822
115064
|
ownTrack: trackId !== "" ? ownTracks.get(trackId) ?? null : null,
|
|
114823
115065
|
ownSubjectBox: trackId !== "" ? subjectBoxes.get(trackId)?.get(copy.sourceMediaId) ?? null : null,
|
|
114824
|
-
neighbours
|
|
115066
|
+
neighbours,
|
|
115067
|
+
...copy.verdictSubject !== void 0 ? { verdictSubject: copy.verdictSubject } : {}
|
|
114825
115068
|
});
|
|
114826
115069
|
out.push({
|
|
114827
115070
|
...withCollections,
|
|
@@ -114831,6 +115074,22 @@ var RetrainService = class {
|
|
|
114831
115074
|
}
|
|
114832
115075
|
return out;
|
|
114833
115076
|
}
|
|
115077
|
+
/** The verdicts of the tracks these frames were copied from. */
|
|
115078
|
+
async verdictsOf(copies) {
|
|
115079
|
+
return this.verdictsOfTracks(copies.flatMap((c) => c.origin === "track" ? [c.trackId] : []));
|
|
115080
|
+
}
|
|
115081
|
+
async verdictsOfTracks(trackIds) {
|
|
115082
|
+
const port = this.deps.verdicts;
|
|
115083
|
+
const ids = [...new Set(trackIds)];
|
|
115084
|
+
if (port === void 0 || ids.length === 0) return /* @__PURE__ */ new Map();
|
|
115085
|
+
return port.forTracks(ids).catch((err) => {
|
|
115086
|
+
this.deps.logger.warn("retrain frames: verdicts unreadable", { meta: {
|
|
115087
|
+
tracks: ids.length,
|
|
115088
|
+
error: String(err)
|
|
115089
|
+
} });
|
|
115090
|
+
return /* @__PURE__ */ new Map();
|
|
115091
|
+
});
|
|
115092
|
+
}
|
|
114834
115093
|
/** The URLs of a frame's pixels, when a data plane is served for them. */
|
|
114835
115094
|
frameUrls(frameId) {
|
|
114836
115095
|
const frameUrl = this.deps.frameUrl;
|
|
@@ -116947,6 +117206,7 @@ var BuildStores = class extends BuildStoresBase {
|
|
|
116947
117206
|
subjectBoxes: (trackId, deviceId) => mediaStore.listSubjectBoxesByOwner("track", trackId, deviceId)
|
|
116948
117207
|
},
|
|
116949
117208
|
memberships: retrainCollectionStore,
|
|
117209
|
+
verdicts: { forTracks: async (trackIds) => this.notificationCenter?.detectionVerdictsOf(trackIds) ?? /* @__PURE__ */ new Map() },
|
|
116950
117210
|
frameUrl: (frameId, size) => retrainFramePath(this.ctx.id, frameId, size)
|
|
116951
117211
|
});
|
|
116952
117212
|
this.retrain = retrain;
|
|
@@ -122658,7 +122918,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
122658
122918
|
onBindingsChanged: (data) => this.handleBindingsChangedEvent(data),
|
|
122659
122919
|
onDeviceUnregistered: (deviceId) => this.handleDeviceUnregisteredEvent(deviceId),
|
|
122660
122920
|
onClusterTopology: (nodes) => this.nodeLiveness.applySnapshot(nodes, Date.now()),
|
|
122661
|
-
onSystemEvent: (event) => this.notificationCenter?.onSystemEvent(event)
|
|
122921
|
+
onSystemEvent: (event) => this.notificationCenter?.onSystemEvent(event),
|
|
122922
|
+
onDeviceRegistryChange: () => this.notificationCenter?.invalidateActionTargets()
|
|
122662
122923
|
});
|
|
122663
122924
|
await step("startTrackingTapes", () => this.startTrackingTapes(this.isPostProcessingNode));
|
|
122664
122925
|
if (this.isPostProcessingNode) await step("startEmbeddingDispatcher", () => this.startEmbeddingDispatcher(logger, transport));
|