@camstack/addon-post-analysis 1.2.347 → 1.2.349
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{clip-model-registry-BftmbgcV.mjs → clip-model-registry-CC-BJYiK.mjs} +1786 -148
- package/dist/{clip-model-registry--UsI8Ud4.js → clip-model-registry-DI12tTgY.js} +1786 -148
- 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-DuQPp3dT.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BYsMflvm.mjs} +3 -3
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Du_tzgi2.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-CQOJKiwa.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-BDQ6oQ7b.mjs → hostInit-Cpi67Icm.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +575 -259
- package/dist/pipeline-analytics/index.mjs +575 -259
- 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-4Fu7jNn9.mjs +0 -26
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-D5IzYeGQ.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-DI12tTgY.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");
|
|
@@ -13391,14 +13391,19 @@ function incomingFromSystemEvent(event, lookup) {
|
|
|
13391
13391
|
case "agent.offline": {
|
|
13392
13392
|
const nodeId = stringField(data, "agentId");
|
|
13393
13393
|
if (nodeId === void 0) return null;
|
|
13394
|
-
const
|
|
13394
|
+
const online = event.category.endsWith(".online");
|
|
13395
|
+
const kind = online ? "node-online" : "node-offline";
|
|
13396
|
+
const reason = online ? void 0 : stringField(data, "reason");
|
|
13395
13397
|
return systemSubject({
|
|
13396
13398
|
facts,
|
|
13397
13399
|
kind,
|
|
13398
13400
|
subject: `node:${nodeId}`,
|
|
13399
13401
|
titleKey: `system.${kind}.title`,
|
|
13400
|
-
bodyKey: `system.${kind}.body.agent`,
|
|
13401
|
-
textParams: {
|
|
13402
|
+
bodyKey: reason === "connection-lost" || reason === "shutdown" ? `system.${kind}.body.agent.${reason}` : `system.${kind}.body.agent`,
|
|
13403
|
+
textParams: {
|
|
13404
|
+
nodeId,
|
|
13405
|
+
nodeName: stringField(data, "nodeName") ?? nodeId
|
|
13406
|
+
},
|
|
13402
13407
|
nodeId
|
|
13403
13408
|
});
|
|
13404
13409
|
}
|
|
@@ -15484,10 +15489,13 @@ var en_default = {
|
|
|
15484
15489
|
"system.stream-offline.title": "Stream offline — {{deviceName}}",
|
|
15485
15490
|
"system.stream-offline.body": "Stream for camera {{deviceName}} is offline",
|
|
15486
15491
|
"system.stream-offline.body.profiled": "{{profile}} stream for camera {{deviceName}} is offline",
|
|
15487
|
-
"system.node-online.title": "Node online — {{
|
|
15488
|
-
"system.node-online.body.agent": "Agent {{
|
|
15489
|
-
"system.node-
|
|
15490
|
-
"system.node-offline.
|
|
15492
|
+
"system.node-online.title": "Node online — {{nodeName}}",
|
|
15493
|
+
"system.node-online.body.agent": "Agent {{nodeName}} is online",
|
|
15494
|
+
"system.node-online.body.agent.after": "Agent {{nodeName}} is back online after {{offlineFor}} offline",
|
|
15495
|
+
"system.node-offline.title": "Node offline — {{nodeName}}",
|
|
15496
|
+
"system.node-offline.body.agent": "Agent {{nodeName}} is offline",
|
|
15497
|
+
"system.node-offline.body.agent.connection-lost": "Agent {{nodeName}} lost its connection to the hub",
|
|
15498
|
+
"system.node-offline.body.agent.shutdown": "Agent {{nodeName}} shut down",
|
|
15491
15499
|
"system.node-inference-unavailable.title": "Node cannot infer — {{nodeId}}",
|
|
15492
15500
|
"system.node-inference-unavailable.body": "Node {{nodeId}} can no longer run inference — every enabled device is unavailable ({{devices}}). It will not receive cameras until one returns.",
|
|
15493
15501
|
"system.addon-crash-loop.title": "Addon crash-loop — {{nodeId}}",
|
|
@@ -15846,10 +15854,13 @@ var it_default = {
|
|
|
15846
15854
|
"system.stream-offline.title": "Stream offline — {{deviceName}}",
|
|
15847
15855
|
"system.stream-offline.body": "Lo stream della camera {{deviceName}} è offline",
|
|
15848
15856
|
"system.stream-offline.body.profiled": "Lo stream {{profile}} della camera {{deviceName}} è offline",
|
|
15849
|
-
"system.node-online.title": "Nodo online — {{
|
|
15850
|
-
"system.node-online.body.agent": "L'agent {{
|
|
15851
|
-
"system.node-
|
|
15852
|
-
"system.node-offline.
|
|
15857
|
+
"system.node-online.title": "Nodo online — {{nodeName}}",
|
|
15858
|
+
"system.node-online.body.agent": "L'agent {{nodeName}} è online",
|
|
15859
|
+
"system.node-online.body.agent.after": "L'agent {{nodeName}} è di nuovo online dopo {{offlineFor}} offline",
|
|
15860
|
+
"system.node-offline.title": "Nodo offline — {{nodeName}}",
|
|
15861
|
+
"system.node-offline.body.agent": "L'agent {{nodeName}} è offline",
|
|
15862
|
+
"system.node-offline.body.agent.connection-lost": "L'agent {{nodeName}} ha perso la connessione con l'hub",
|
|
15863
|
+
"system.node-offline.body.agent.shutdown": "L'agent {{nodeName}} si è spento",
|
|
15853
15864
|
"system.node-inference-unavailable.title": "Nodo senza inferenza — {{nodeId}}",
|
|
15854
15865
|
"system.node-inference-unavailable.body": "Il nodo {{nodeId}} non può più eseguire inferenza — tutti i dispositivi abilitati non sono disponibili ({{devices}}). Non riceverà camere finché uno non torna disponibile.",
|
|
15855
15866
|
"system.addon-crash-loop.title": "Crash-loop addon — {{nodeId}}",
|
|
@@ -18478,6 +18489,7 @@ function planRuleTest(input) {
|
|
|
18478
18489
|
deviceId: String(systemScope.deviceId ?? deviceId),
|
|
18479
18490
|
deviceName: `camera ${String(systemScope.deviceId ?? deviceId)}`,
|
|
18480
18491
|
nodeId: systemScope.nodeId ?? "hub",
|
|
18492
|
+
nodeName: systemScope.nodeId ?? "hub",
|
|
18481
18493
|
packageName: systemScope.packageName ?? "@camstack/server",
|
|
18482
18494
|
currentVersion: "1.0.0",
|
|
18483
18495
|
latestVersion: "1.0.1",
|
|
@@ -26972,6 +26984,7 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
|
|
|
26972
26984
|
currentVersion: typeof systemEvent?.data["currentVersion"] === "string" ? systemEvent.data["currentVersion"] : "",
|
|
26973
26985
|
latestVersion: typeof systemEvent?.data["latestVersion"] === "string" ? systemEvent.data["latestVersion"] : "",
|
|
26974
26986
|
...systemEvent?.deviceId !== void 0 ? { deviceName: String(systemEvent.deviceId) } : {},
|
|
26987
|
+
...systemEvent?.nodeId !== void 0 && (systemEvent.kind === "node-online" || systemEvent.kind === "node-offline") ? { nodeName: systemEvent.nodeId } : {},
|
|
26975
26988
|
...systemEvent?.textParams
|
|
26976
26989
|
};
|
|
26977
26990
|
}
|
|
@@ -28922,6 +28935,11 @@ var NcJudgedTracks = class {
|
|
|
28922
28935
|
//#endregion
|
|
28923
28936
|
//#region src/notification-center/liveness-holdoff.ts
|
|
28924
28937
|
var DEVICE_LIVENESS_HOLDOFF_MS = 5 * 6e4;
|
|
28938
|
+
/** Grace before a node offline (or its recovery) is announced — see the docblock. */
|
|
28939
|
+
var NODE_LIVENESS_HOLDOFF_MS = 6e4;
|
|
28940
|
+
function isNodeLivenessKind(kind) {
|
|
28941
|
+
return kind === "node-online" || kind === "node-offline";
|
|
28942
|
+
}
|
|
28925
28943
|
function isDeviceLivenessKind(kind) {
|
|
28926
28944
|
return kind === "device-online" || kind === "device-offline" || kind === "camera-online" || kind === "camera-offline";
|
|
28927
28945
|
}
|
|
@@ -28933,6 +28951,10 @@ var DeviceLivenessHoldoff = class {
|
|
|
28933
28951
|
pending = /* @__PURE__ */ new Map();
|
|
28934
28952
|
lastNotified = /* @__PURE__ */ new Map();
|
|
28935
28953
|
timers = /* @__PURE__ */ new Map();
|
|
28954
|
+
/** First divergence from the last notified state, per subject (cleared on a bounce back). */
|
|
28955
|
+
departedAt = /* @__PURE__ */ new Map();
|
|
28956
|
+
/** When the last notified state began, per subject. */
|
|
28957
|
+
stateSince = /* @__PURE__ */ new Map();
|
|
28936
28958
|
delayMs;
|
|
28937
28959
|
schedule;
|
|
28938
28960
|
cancel;
|
|
@@ -28945,6 +28967,11 @@ var DeviceLivenessHoldoff = class {
|
|
|
28945
28967
|
/** The seeded (or last delivered) state — a bounce back to this is silence. */
|
|
28946
28968
|
remember(subject, state) {
|
|
28947
28969
|
this.lastNotified.set(subject, state);
|
|
28970
|
+
const pending = this.timers.get(subject);
|
|
28971
|
+
if (pending !== void 0) this.cancel(pending);
|
|
28972
|
+
this.timers.delete(subject);
|
|
28973
|
+
this.pending.delete(subject);
|
|
28974
|
+
this.departedAt.delete(subject);
|
|
28948
28975
|
}
|
|
28949
28976
|
/** Directory-seeded rows never pass through {@link remember}; first flip does. */
|
|
28950
28977
|
rememberIfAbsent(subject, state) {
|
|
@@ -28953,6 +28980,7 @@ var DeviceLivenessHoldoff = class {
|
|
|
28953
28980
|
/** A real ledger flip. Starts / resets the trailing timer. */
|
|
28954
28981
|
arm(subject, incoming) {
|
|
28955
28982
|
this.pending.set(subject, incoming);
|
|
28983
|
+
this.trackDeparture(subject, incoming);
|
|
28956
28984
|
const existing = this.timers.get(subject);
|
|
28957
28985
|
if (existing !== void 0) this.cancel(existing);
|
|
28958
28986
|
if (this.delayMs <= 0) {
|
|
@@ -28970,6 +28998,16 @@ var DeviceLivenessHoldoff = class {
|
|
|
28970
28998
|
for (const id of this.timers.values()) this.cancel(id);
|
|
28971
28999
|
this.timers.clear();
|
|
28972
29000
|
this.pending.clear();
|
|
29001
|
+
this.departedAt.clear();
|
|
29002
|
+
}
|
|
29003
|
+
trackDeparture(subject, incoming) {
|
|
29004
|
+
const kind = incoming.subject.systemEvent?.kind;
|
|
29005
|
+
if (kind === void 0) return;
|
|
29006
|
+
if (this.lastNotified.get(subject) === livenessStateOfKind(kind)) {
|
|
29007
|
+
this.departedAt.delete(subject);
|
|
29008
|
+
return;
|
|
29009
|
+
}
|
|
29010
|
+
if (!this.departedAt.has(subject)) this.departedAt.set(subject, incoming.subject.timestamp);
|
|
28973
29011
|
}
|
|
28974
29012
|
fire(subject) {
|
|
28975
29013
|
const incoming = this.pending.get(subject);
|
|
@@ -28980,10 +29018,64 @@ var DeviceLivenessHoldoff = class {
|
|
|
28980
29018
|
const state = livenessStateOfKind(kind);
|
|
28981
29019
|
if (this.lastNotified.get(subject) === state) return;
|
|
28982
29020
|
this.lastNotified.set(subject, state);
|
|
28983
|
-
this.
|
|
29021
|
+
const since = this.departedAt.get(subject) ?? incoming.subject.timestamp;
|
|
29022
|
+
const previousSince = this.stateSince.get(subject);
|
|
29023
|
+
this.departedAt.delete(subject);
|
|
29024
|
+
this.stateSince.set(subject, since);
|
|
29025
|
+
this.flush(incoming, previousSince !== void 0 ? {
|
|
29026
|
+
since,
|
|
29027
|
+
previousSince
|
|
29028
|
+
} : { since });
|
|
28984
29029
|
}
|
|
28985
29030
|
};
|
|
28986
29031
|
//#endregion
|
|
29032
|
+
//#region src/notification-center/node-liveness-text.ts
|
|
29033
|
+
/** "45 s", "12 min", "2 h 5 min", "3 d 4 h" — unit symbols read the same in en and it. */
|
|
29034
|
+
function formatOutageDuration(ms) {
|
|
29035
|
+
const totalSec = Math.max(0, Math.round(ms / 1e3));
|
|
29036
|
+
if (totalSec < 60) return `${totalSec} s`;
|
|
29037
|
+
const totalMin = Math.floor(totalSec / 60);
|
|
29038
|
+
if (totalMin < 60) return `${totalMin} min`;
|
|
29039
|
+
const totalHours = Math.floor(totalMin / 60);
|
|
29040
|
+
const minutes = totalMin % 60;
|
|
29041
|
+
if (totalHours < 24) return minutes > 0 ? `${totalHours} h ${minutes} min` : `${totalHours} h`;
|
|
29042
|
+
const days = Math.floor(totalHours / 24);
|
|
29043
|
+
const hours = totalHours % 24;
|
|
29044
|
+
return hours > 0 ? `${days} d ${hours} h` : `${days} d`;
|
|
29045
|
+
}
|
|
29046
|
+
/**
|
|
29047
|
+
* Freeze the post-hold-off facts onto a node liveness event.
|
|
29048
|
+
*
|
|
29049
|
+
* - `nodeName`: the display name, when the directory knew one.
|
|
29050
|
+
* - `offlineFor` (recovery only, and only when the outage START is known — a
|
|
29051
|
+
* state that was merely seeded after a restart has none): the outage length,
|
|
29052
|
+
* with the `.after` body variant that says it.
|
|
29053
|
+
*/
|
|
29054
|
+
function withNodeLivenessFacts(incoming, facts) {
|
|
29055
|
+
const system = incoming.subject.systemEvent;
|
|
29056
|
+
if (system === void 0) return incoming;
|
|
29057
|
+
const { since, previousSince } = facts.timing;
|
|
29058
|
+
const recovered = system.kind === "node-online" && previousSince !== void 0;
|
|
29059
|
+
const name = facts.nodeName !== void 0 && facts.nodeName.length > 0 ? facts.nodeName : void 0;
|
|
29060
|
+
if (!recovered && name === void 0) return incoming;
|
|
29061
|
+
const textParams = {
|
|
29062
|
+
...system.textParams,
|
|
29063
|
+
...name !== void 0 ? { nodeName: name } : {},
|
|
29064
|
+
...recovered ? { offlineFor: formatOutageDuration(since - previousSince) } : {}
|
|
29065
|
+
};
|
|
29066
|
+
return {
|
|
29067
|
+
...incoming,
|
|
29068
|
+
subject: {
|
|
29069
|
+
...incoming.subject,
|
|
29070
|
+
systemEvent: {
|
|
29071
|
+
...system,
|
|
29072
|
+
...recovered ? { bodyKey: "system.node-online.body.agent.after" } : {},
|
|
29073
|
+
textParams
|
|
29074
|
+
}
|
|
29075
|
+
}
|
|
29076
|
+
};
|
|
29077
|
+
}
|
|
29078
|
+
//#endregion
|
|
28987
29079
|
//#region src/notification-center/linked-camera-media.ts
|
|
28988
29080
|
/** ADR 0632: absent = mosaic, applied HERE and never as a Zod default. */
|
|
28989
29081
|
function linkedCameraMode(media) {
|
|
@@ -38303,6 +38395,8 @@ var ALARM_PANEL_CAP = require_clip_model_registry.alarmPanelCapability.name;
|
|
|
38303
38395
|
/** Bound on remembered departure fallbacks (D691 review M3) — the shelf's own
|
|
38304
38396
|
* capture bound. */
|
|
38305
38397
|
var NC_DEPARTURE_FALLBACK_MAX = 64;
|
|
38398
|
+
/** Bound on the post-hold-off `nodes.directory` read that names a node. */
|
|
38399
|
+
var NODE_NAME_READ_TIMEOUT_MS = 3e3;
|
|
38306
38400
|
/** Bound on the sampled "departed track alias resolution failed" memo
|
|
38307
38401
|
* (D683 × D691) — the alias-resolution call sites' own shape. */
|
|
38308
38402
|
var NC_DEPARTED_ALIAS_FAILURE_MEMO_MAX = 256;
|
|
@@ -39099,6 +39193,8 @@ var NotificationCenter = class {
|
|
|
39099
39193
|
addonUpdates;
|
|
39100
39194
|
/** Trailing 5 min hold-off for camera liveness flaps (plugin restart). */
|
|
39101
39195
|
livenessHoldoff;
|
|
39196
|
+
/** Trailing 60 s hold-off for node liveness (reconnect storms). */
|
|
39197
|
+
nodeLivenessHoldoff;
|
|
39102
39198
|
/**
|
|
39103
39199
|
* Serialized evaluation chain. The persist hooks are fire-and-forget for
|
|
39104
39200
|
* the frame path, but two concurrent evaluations of the same device
|
|
@@ -39800,6 +39896,7 @@ var NotificationCenter = class {
|
|
|
39800
39896
|
if (incoming !== null) this.consumeEvent(incoming);
|
|
39801
39897
|
});
|
|
39802
39898
|
this.livenessHoldoff = new DeviceLivenessHoldoff((event) => this.consumeEvent(event), { delayMs: deps.livenessHoldoffMs ?? (process.env.VITEST ? 0 : DEVICE_LIVENESS_HOLDOFF_MS) });
|
|
39899
|
+
this.nodeLivenessHoldoff = new DeviceLivenessHoldoff((event, timing) => this.consumeNodeLiveness(event, timing), { delayMs: deps.nodeLivenessHoldoffMs ?? (process.env.VITEST ? 0 : NODE_LIVENESS_HOLDOFF_MS) });
|
|
39803
39900
|
this.actionRunner = deps.actuate !== void 0 && deps.isCapDeviceScoped !== void 0 ? new NcRuleActionRunner({
|
|
39804
39901
|
logger: this.logger.child("actions"),
|
|
39805
39902
|
actuate: (input) => this.actuateWithAttribution(input),
|
|
@@ -40361,6 +40458,7 @@ var NotificationCenter = class {
|
|
|
40361
40458
|
this.linkedStills?.clear();
|
|
40362
40459
|
this.addonUpdates.dispose();
|
|
40363
40460
|
this.livenessHoldoff.dispose();
|
|
40461
|
+
this.nodeLivenessHoldoff.dispose();
|
|
40364
40462
|
this.evaluationActive = false;
|
|
40365
40463
|
this.hydration = null;
|
|
40366
40464
|
}
|
|
@@ -40433,6 +40531,42 @@ var NotificationCenter = class {
|
|
|
40433
40531
|
this.scheduleEvaluation(event);
|
|
40434
40532
|
}
|
|
40435
40533
|
/**
|
|
40534
|
+
* A node liveness flip that survived its hold-off: freeze the node's display
|
|
40535
|
+
* name and (on a recovery) the outage length onto it, then consume it.
|
|
40536
|
+
*
|
|
40537
|
+
* The name read is bounded and best-effort — a slow or failed directory
|
|
40538
|
+
* costs the friendly name, never the notification (the intake already froze
|
|
40539
|
+
* the id as `nodeName`).
|
|
40540
|
+
*/
|
|
40541
|
+
consumeNodeLiveness(event, timing) {
|
|
40542
|
+
const nodeId = event.subject.systemEvent?.nodeId;
|
|
40543
|
+
this.readNodeDisplayName(nodeId).then((nodeName) => {
|
|
40544
|
+
this.consumeEvent(withNodeLivenessFacts(event, nodeName !== void 0 ? {
|
|
40545
|
+
timing,
|
|
40546
|
+
nodeName
|
|
40547
|
+
} : { timing }));
|
|
40548
|
+
});
|
|
40549
|
+
}
|
|
40550
|
+
async readNodeDisplayName(nodeId) {
|
|
40551
|
+
const read = this.deps.readNodeDirectory;
|
|
40552
|
+
if (nodeId === void 0 || read === void 0) return void 0;
|
|
40553
|
+
let timer;
|
|
40554
|
+
try {
|
|
40555
|
+
const timeout = new Promise((resolve) => {
|
|
40556
|
+
timer = setTimeout(() => resolve(void 0), NODE_NAME_READ_TIMEOUT_MS);
|
|
40557
|
+
});
|
|
40558
|
+
return (await Promise.race([read(), timeout]))?.nodes.find((node) => node.id === nodeId)?.name;
|
|
40559
|
+
} catch (err) {
|
|
40560
|
+
this.logger.debug("node display name unavailable — notifying with the id", { meta: {
|
|
40561
|
+
nodeId,
|
|
40562
|
+
error: String(err)
|
|
40563
|
+
} });
|
|
40564
|
+
return;
|
|
40565
|
+
} finally {
|
|
40566
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
40567
|
+
}
|
|
40568
|
+
}
|
|
40569
|
+
/**
|
|
40436
40570
|
* The same consumption, awaited, with the verdict returned. Identical code
|
|
40437
40571
|
* path — {@link consumeEvent} is this function with the report discarded — so
|
|
40438
40572
|
* a producer that reads the report is testing what the live producers do, not
|
|
@@ -41365,8 +41499,9 @@ var NotificationCenter = class {
|
|
|
41365
41499
|
if (observation !== null) {
|
|
41366
41500
|
const previous = this.liveness.stateOf(observation.subject);
|
|
41367
41501
|
const verdict = this.liveness.observe(observation, this.now());
|
|
41502
|
+
const holdoff = isNodeLivenessKind(observation.kind) ? this.nodeLivenessHoldoff : this.livenessHoldoff;
|
|
41368
41503
|
if (verdict === "seeded") {
|
|
41369
|
-
|
|
41504
|
+
holdoff.remember(observation.subject, observation.state);
|
|
41370
41505
|
this.reportLivenessDrop(incoming, verdict);
|
|
41371
41506
|
return;
|
|
41372
41507
|
}
|
|
@@ -41374,9 +41509,9 @@ var NotificationCenter = class {
|
|
|
41374
41509
|
this.reportLivenessDrop(incoming, verdict);
|
|
41375
41510
|
return;
|
|
41376
41511
|
}
|
|
41377
|
-
if (previous !== void 0)
|
|
41512
|
+
if (previous !== void 0) holdoff.rememberIfAbsent(observation.subject, previous);
|
|
41378
41513
|
if (observation.state === "online" && !this.deviceAlertSettings().notifyRecovery) {
|
|
41379
|
-
|
|
41514
|
+
holdoff.remember(observation.subject, observation.state);
|
|
41380
41515
|
this.logger.debug("device came back — the all-clear is switched off", {
|
|
41381
41516
|
tags: incoming.log().tags,
|
|
41382
41517
|
meta: {
|
|
@@ -41386,8 +41521,8 @@ var NotificationCenter = class {
|
|
|
41386
41521
|
});
|
|
41387
41522
|
return;
|
|
41388
41523
|
}
|
|
41389
|
-
if (isDeviceLivenessKind(observation.kind)) {
|
|
41390
|
-
|
|
41524
|
+
if (isDeviceLivenessKind(observation.kind) || isNodeLivenessKind(observation.kind)) {
|
|
41525
|
+
holdoff.arm(observation.subject, incoming);
|
|
41391
41526
|
return;
|
|
41392
41527
|
}
|
|
41393
41528
|
} else if (system !== void 0 && (system.kind.startsWith("device-") || system.kind.startsWith("stream-"))) {
|
|
@@ -83507,6 +83642,260 @@ function withDatasetVersion(manifest, version) {
|
|
|
83507
83642
|
};
|
|
83508
83643
|
}
|
|
83509
83644
|
//#endregion
|
|
83645
|
+
//#region src/pipeline-analytics/retrain/retrain-dataset-version-store.ts
|
|
83646
|
+
/**
|
|
83647
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
83648
|
+
* write="one row per dataset version the operator creates: a frozen frameIds list,
|
|
83649
|
+
* the sha256 of the annotations those frames carried, the export parameters and
|
|
83650
|
+
* the counts at creation. Never updated after the insert — a different selection
|
|
83651
|
+
* is a new version."
|
|
83652
|
+
* retention="none — rows leave only when the operator deletes the version. The
|
|
83653
|
+
* frames themselves are owned by the retrain corpus; deleting a version deletes no
|
|
83654
|
+
* frame, and deleting a frame does not edit a version (verify reports it missing)."
|
|
83655
|
+
*/
|
|
83656
|
+
var RETRAIN_DATASET_VERSIONS_COLLECTION = "pipeline-analytics:retrain-dataset-versions";
|
|
83657
|
+
var COLUMNS = [
|
|
83658
|
+
{
|
|
83659
|
+
name: "id",
|
|
83660
|
+
type: "TEXT",
|
|
83661
|
+
primaryKey: true,
|
|
83662
|
+
notNull: true
|
|
83663
|
+
},
|
|
83664
|
+
{
|
|
83665
|
+
name: "name",
|
|
83666
|
+
type: "TEXT",
|
|
83667
|
+
notNull: true
|
|
83668
|
+
},
|
|
83669
|
+
{
|
|
83670
|
+
name: "nameKey",
|
|
83671
|
+
type: "TEXT",
|
|
83672
|
+
notNull: true
|
|
83673
|
+
},
|
|
83674
|
+
{
|
|
83675
|
+
name: "createdAt",
|
|
83676
|
+
type: "INTEGER",
|
|
83677
|
+
notNull: true
|
|
83678
|
+
},
|
|
83679
|
+
{
|
|
83680
|
+
name: "selection",
|
|
83681
|
+
type: "JSON",
|
|
83682
|
+
notNull: true
|
|
83683
|
+
},
|
|
83684
|
+
{
|
|
83685
|
+
name: "layout",
|
|
83686
|
+
type: "TEXT",
|
|
83687
|
+
notNull: true
|
|
83688
|
+
},
|
|
83689
|
+
{
|
|
83690
|
+
name: "completeOnly",
|
|
83691
|
+
type: "BOOLEAN",
|
|
83692
|
+
notNull: true
|
|
83693
|
+
},
|
|
83694
|
+
{
|
|
83695
|
+
name: "frameIds",
|
|
83696
|
+
type: "JSON",
|
|
83697
|
+
notNull: true
|
|
83698
|
+
},
|
|
83699
|
+
{
|
|
83700
|
+
name: "annotationHash",
|
|
83701
|
+
type: "TEXT",
|
|
83702
|
+
notNull: true
|
|
83703
|
+
},
|
|
83704
|
+
{
|
|
83705
|
+
name: "manifestSchema",
|
|
83706
|
+
type: "TEXT",
|
|
83707
|
+
notNull: true
|
|
83708
|
+
},
|
|
83709
|
+
{
|
|
83710
|
+
name: "stats",
|
|
83711
|
+
type: "JSON",
|
|
83712
|
+
notNull: true
|
|
83713
|
+
}
|
|
83714
|
+
];
|
|
83715
|
+
var INDEXES = [{
|
|
83716
|
+
name: "idx_retrain_dataset_versions_name_key",
|
|
83717
|
+
columns: ["nameKey"],
|
|
83718
|
+
unique: true
|
|
83719
|
+
}, {
|
|
83720
|
+
name: "idx_retrain_dataset_versions_created",
|
|
83721
|
+
columns: ["createdAt"]
|
|
83722
|
+
}];
|
|
83723
|
+
function summaryOfVersion(version) {
|
|
83724
|
+
const { frameIds, ...rest } = version;
|
|
83725
|
+
return {
|
|
83726
|
+
...rest,
|
|
83727
|
+
frameCount: frameIds.length
|
|
83728
|
+
};
|
|
83729
|
+
}
|
|
83730
|
+
var nameKeyOf = (name) => name.trim().toLowerCase();
|
|
83731
|
+
function numberList(value) {
|
|
83732
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === "number") : void 0;
|
|
83733
|
+
}
|
|
83734
|
+
function stringList(value) {
|
|
83735
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : void 0;
|
|
83736
|
+
}
|
|
83737
|
+
function parseJson(value) {
|
|
83738
|
+
if (typeof value !== "string") return value;
|
|
83739
|
+
try {
|
|
83740
|
+
return JSON.parse(value);
|
|
83741
|
+
} catch {
|
|
83742
|
+
return;
|
|
83743
|
+
}
|
|
83744
|
+
}
|
|
83745
|
+
function asRecord(value) {
|
|
83746
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
83747
|
+
}
|
|
83748
|
+
var EMPTY_STATS = {
|
|
83749
|
+
frames: 0,
|
|
83750
|
+
completeFrames: 0,
|
|
83751
|
+
completeness: 0,
|
|
83752
|
+
subjects: 0,
|
|
83753
|
+
modelErrors: 0,
|
|
83754
|
+
hardNegatives: 0,
|
|
83755
|
+
cells: [],
|
|
83756
|
+
cameras: []
|
|
83757
|
+
};
|
|
83758
|
+
/** A stored stats object, trusted only as far as its shape. */
|
|
83759
|
+
function readStats(value) {
|
|
83760
|
+
const raw = asRecord(parseJson(value));
|
|
83761
|
+
const num = (k) => typeof raw[k] === "number" ? raw[k] : 0;
|
|
83762
|
+
const cells = Array.isArray(raw["cells"]) ? raw["cells"] : [];
|
|
83763
|
+
const cameras = Array.isArray(raw["cameras"]) ? raw["cameras"] : [];
|
|
83764
|
+
return {
|
|
83765
|
+
...EMPTY_STATS,
|
|
83766
|
+
frames: num("frames"),
|
|
83767
|
+
completeFrames: num("completeFrames"),
|
|
83768
|
+
completeness: num("completeness"),
|
|
83769
|
+
subjects: num("subjects"),
|
|
83770
|
+
modelErrors: num("modelErrors"),
|
|
83771
|
+
hardNegatives: num("hardNegatives"),
|
|
83772
|
+
cells: cells.map((c) => {
|
|
83773
|
+
const r = asRecord(c);
|
|
83774
|
+
const lighting = r["lighting"] === "day" || r["lighting"] === "night" ? r["lighting"] : "unknown";
|
|
83775
|
+
return {
|
|
83776
|
+
macroClass: String(r["macroClass"] ?? ""),
|
|
83777
|
+
deviceId: Number(r["deviceId"] ?? 0),
|
|
83778
|
+
lighting,
|
|
83779
|
+
subjects: Number(r["subjects"] ?? 0),
|
|
83780
|
+
frames: Number(r["frames"] ?? 0)
|
|
83781
|
+
};
|
|
83782
|
+
}),
|
|
83783
|
+
cameras: cameras.map((c) => {
|
|
83784
|
+
const r = asRecord(c);
|
|
83785
|
+
return {
|
|
83786
|
+
deviceId: Number(r["deviceId"] ?? 0),
|
|
83787
|
+
frames: Number(r["frames"] ?? 0),
|
|
83788
|
+
completeFrames: Number(r["completeFrames"] ?? 0)
|
|
83789
|
+
};
|
|
83790
|
+
})
|
|
83791
|
+
};
|
|
83792
|
+
}
|
|
83793
|
+
var RetrainDatasetVersionStore = class {
|
|
83794
|
+
store;
|
|
83795
|
+
logger;
|
|
83796
|
+
constructor(deps) {
|
|
83797
|
+
this.store = deps.store;
|
|
83798
|
+
this.logger = deps.logger;
|
|
83799
|
+
}
|
|
83800
|
+
static async declare(store) {
|
|
83801
|
+
await store.declareCollection.mutate({
|
|
83802
|
+
collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
|
|
83803
|
+
columns: [...COLUMNS],
|
|
83804
|
+
indexes: [...INDEXES]
|
|
83805
|
+
});
|
|
83806
|
+
}
|
|
83807
|
+
insert = async (version) => {
|
|
83808
|
+
await this.store.set.mutate({
|
|
83809
|
+
collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
|
|
83810
|
+
key: version.id,
|
|
83811
|
+
value: {
|
|
83812
|
+
name: version.name,
|
|
83813
|
+
nameKey: nameKeyOf(version.name),
|
|
83814
|
+
createdAt: version.createdAt,
|
|
83815
|
+
selection: version.selection,
|
|
83816
|
+
layout: version.layout,
|
|
83817
|
+
completeOnly: version.completeOnly,
|
|
83818
|
+
frameIds: version.frameIds,
|
|
83819
|
+
annotationHash: version.annotationHash,
|
|
83820
|
+
manifestSchema: version.manifestSchema,
|
|
83821
|
+
stats: version.stats
|
|
83822
|
+
}
|
|
83823
|
+
});
|
|
83824
|
+
};
|
|
83825
|
+
get = async (id) => {
|
|
83826
|
+
try {
|
|
83827
|
+
const row = await this.store.get.query({
|
|
83828
|
+
collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
|
|
83829
|
+
key: id
|
|
83830
|
+
});
|
|
83831
|
+
return row === null || row === void 0 ? null : this.toVersion(id, asRecord(row));
|
|
83832
|
+
} catch (err) {
|
|
83833
|
+
this.logger.warn("retrain dataset version read failed", { meta: {
|
|
83834
|
+
versionId: id,
|
|
83835
|
+
error: String(err)
|
|
83836
|
+
} });
|
|
83837
|
+
return null;
|
|
83838
|
+
}
|
|
83839
|
+
};
|
|
83840
|
+
list = async () => {
|
|
83841
|
+
return (await this.listRows()).map(summaryOfVersion);
|
|
83842
|
+
};
|
|
83843
|
+
/** Every row WITH its frame list — the migration into `training` reads this. */
|
|
83844
|
+
listRows = async () => {
|
|
83845
|
+
try {
|
|
83846
|
+
return (await this.store.query.query({
|
|
83847
|
+
collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
|
|
83848
|
+
filter: { orderBy: {
|
|
83849
|
+
field: "createdAt",
|
|
83850
|
+
direction: "desc"
|
|
83851
|
+
} }
|
|
83852
|
+
})).map((r) => this.toVersion(r.id, asRecord(r.data)));
|
|
83853
|
+
} catch (err) {
|
|
83854
|
+
this.logger.warn("retrain dataset versions list failed", { meta: { error: String(err) } });
|
|
83855
|
+
return [];
|
|
83856
|
+
}
|
|
83857
|
+
};
|
|
83858
|
+
findIdByName = async (name) => {
|
|
83859
|
+
return (await this.store.query.query({
|
|
83860
|
+
collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
|
|
83861
|
+
filter: {
|
|
83862
|
+
where: { nameKey: nameKeyOf(name) },
|
|
83863
|
+
limit: 1
|
|
83864
|
+
}
|
|
83865
|
+
}))[0]?.id ?? null;
|
|
83866
|
+
};
|
|
83867
|
+
remove = async (id) => {
|
|
83868
|
+
if (await this.get(id) === null) return false;
|
|
83869
|
+
await this.store.delete.mutate({
|
|
83870
|
+
collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
|
|
83871
|
+
key: id
|
|
83872
|
+
});
|
|
83873
|
+
return true;
|
|
83874
|
+
};
|
|
83875
|
+
toVersion(id, data) {
|
|
83876
|
+
const selection = asRecord(parseJson(data["selection"]));
|
|
83877
|
+
const deviceIds = numberList(selection["deviceIds"]);
|
|
83878
|
+
const collectionIds = stringList(selection["collectionIds"]);
|
|
83879
|
+
const frameIdsSel = stringList(selection["frameIds"]);
|
|
83880
|
+
return {
|
|
83881
|
+
id,
|
|
83882
|
+
name: String(data["name"] ?? ""),
|
|
83883
|
+
createdAt: Number(data["createdAt"] ?? 0),
|
|
83884
|
+
selection: {
|
|
83885
|
+
...deviceIds !== void 0 ? { deviceIds } : {},
|
|
83886
|
+
...collectionIds !== void 0 ? { collectionIds } : {},
|
|
83887
|
+
...frameIdsSel !== void 0 ? { frameIds: frameIdsSel } : {}
|
|
83888
|
+
},
|
|
83889
|
+
layout: data["layout"] === "crops" ? "crops" : "frames",
|
|
83890
|
+
completeOnly: data["completeOnly"] === true || data["completeOnly"] === 1,
|
|
83891
|
+
frameIds: stringList(parseJson(data["frameIds"])) ?? [],
|
|
83892
|
+
annotationHash: String(data["annotationHash"] ?? ""),
|
|
83893
|
+
manifestSchema: String(data["manifestSchema"] ?? ""),
|
|
83894
|
+
stats: readStats(data["stats"])
|
|
83895
|
+
};
|
|
83896
|
+
}
|
|
83897
|
+
};
|
|
83898
|
+
//#endregion
|
|
83510
83899
|
//#region src/pipeline-analytics/retrain/retrain-geometry.ts
|
|
83511
83900
|
function clamp01(v) {
|
|
83512
83901
|
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
@@ -83795,13 +84184,6 @@ function filterOf(selection, completeOnly) {
|
|
|
83795
84184
|
...completeOnly ? { completeOnly: true } : {}
|
|
83796
84185
|
};
|
|
83797
84186
|
}
|
|
83798
|
-
function summaryOf(version) {
|
|
83799
|
-
const { frameIds, ...rest } = version;
|
|
83800
|
-
return {
|
|
83801
|
-
...rest,
|
|
83802
|
-
frameCount: frameIds.length
|
|
83803
|
-
};
|
|
83804
|
-
}
|
|
83805
84187
|
var RetrainDatasetService = class {
|
|
83806
84188
|
deps;
|
|
83807
84189
|
constructor(deps) {
|
|
@@ -83854,11 +84236,11 @@ var RetrainDatasetService = class {
|
|
|
83854
84236
|
} });
|
|
83855
84237
|
return {
|
|
83856
84238
|
kind: "created",
|
|
83857
|
-
version:
|
|
84239
|
+
version: summaryOfVersion(version)
|
|
83858
84240
|
};
|
|
83859
84241
|
}
|
|
83860
84242
|
async list() {
|
|
83861
|
-
return
|
|
84243
|
+
return this.deps.versions.list();
|
|
83862
84244
|
}
|
|
83863
84245
|
async delete(input) {
|
|
83864
84246
|
const deleted = await this.deps.versions.remove(input.id);
|
|
@@ -83916,246 +84298,62 @@ var RetrainDatasetService = class {
|
|
|
83916
84298
|
}
|
|
83917
84299
|
};
|
|
83918
84300
|
//#endregion
|
|
83919
|
-
//#region src/pipeline-analytics/retrain/retrain-dataset-version-
|
|
83920
|
-
|
|
83921
|
-
|
|
83922
|
-
|
|
83923
|
-
|
|
83924
|
-
|
|
83925
|
-
* is a new version."
|
|
83926
|
-
* retention="none — rows leave only when the operator deletes the version. The
|
|
83927
|
-
* frames themselves are owned by the retrain corpus; deleting a version deletes no
|
|
83928
|
-
* frame, and deleting a frame does not edit a version (verify reports it missing)."
|
|
83929
|
-
*/
|
|
83930
|
-
var RETRAIN_DATASET_VERSIONS_COLLECTION = "pipeline-analytics:retrain-dataset-versions";
|
|
83931
|
-
var COLUMNS = [
|
|
83932
|
-
{
|
|
83933
|
-
name: "id",
|
|
83934
|
-
type: "TEXT",
|
|
83935
|
-
primaryKey: true,
|
|
83936
|
-
notNull: true
|
|
83937
|
-
},
|
|
83938
|
-
{
|
|
83939
|
-
name: "name",
|
|
83940
|
-
type: "TEXT",
|
|
83941
|
-
notNull: true
|
|
83942
|
-
},
|
|
83943
|
-
{
|
|
83944
|
-
name: "nameKey",
|
|
83945
|
-
type: "TEXT",
|
|
83946
|
-
notNull: true
|
|
83947
|
-
},
|
|
83948
|
-
{
|
|
83949
|
-
name: "createdAt",
|
|
83950
|
-
type: "INTEGER",
|
|
83951
|
-
notNull: true
|
|
83952
|
-
},
|
|
83953
|
-
{
|
|
83954
|
-
name: "selection",
|
|
83955
|
-
type: "JSON",
|
|
83956
|
-
notNull: true
|
|
83957
|
-
},
|
|
83958
|
-
{
|
|
83959
|
-
name: "layout",
|
|
83960
|
-
type: "TEXT",
|
|
83961
|
-
notNull: true
|
|
83962
|
-
},
|
|
83963
|
-
{
|
|
83964
|
-
name: "completeOnly",
|
|
83965
|
-
type: "BOOLEAN",
|
|
83966
|
-
notNull: true
|
|
83967
|
-
},
|
|
83968
|
-
{
|
|
83969
|
-
name: "frameIds",
|
|
83970
|
-
type: "JSON",
|
|
83971
|
-
notNull: true
|
|
83972
|
-
},
|
|
83973
|
-
{
|
|
83974
|
-
name: "annotationHash",
|
|
83975
|
-
type: "TEXT",
|
|
83976
|
-
notNull: true
|
|
83977
|
-
},
|
|
83978
|
-
{
|
|
83979
|
-
name: "manifestSchema",
|
|
83980
|
-
type: "TEXT",
|
|
83981
|
-
notNull: true
|
|
83982
|
-
},
|
|
83983
|
-
{
|
|
83984
|
-
name: "stats",
|
|
83985
|
-
type: "JSON",
|
|
83986
|
-
notNull: true
|
|
83987
|
-
}
|
|
83988
|
-
];
|
|
83989
|
-
var INDEXES = [{
|
|
83990
|
-
name: "idx_retrain_dataset_versions_name_key",
|
|
83991
|
-
columns: ["nameKey"],
|
|
83992
|
-
unique: true
|
|
83993
|
-
}, {
|
|
83994
|
-
name: "idx_retrain_dataset_versions_created",
|
|
83995
|
-
columns: ["createdAt"]
|
|
83996
|
-
}];
|
|
83997
|
-
var nameKeyOf = (name) => name.trim().toLowerCase();
|
|
83998
|
-
function numberList(value) {
|
|
83999
|
-
return Array.isArray(value) ? value.filter((v) => typeof v === "number") : void 0;
|
|
84000
|
-
}
|
|
84001
|
-
function stringList(value) {
|
|
84002
|
-
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : void 0;
|
|
84003
|
-
}
|
|
84004
|
-
function parseJson(value) {
|
|
84005
|
-
if (typeof value !== "string") return value;
|
|
84006
|
-
try {
|
|
84007
|
-
return JSON.parse(value);
|
|
84008
|
-
} catch {
|
|
84009
|
-
return;
|
|
84010
|
-
}
|
|
84011
|
-
}
|
|
84012
|
-
function asRecord(value) {
|
|
84013
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
84014
|
-
}
|
|
84015
|
-
var EMPTY_STATS = {
|
|
84016
|
-
frames: 0,
|
|
84017
|
-
completeFrames: 0,
|
|
84018
|
-
completeness: 0,
|
|
84019
|
-
subjects: 0,
|
|
84020
|
-
modelErrors: 0,
|
|
84021
|
-
hardNegatives: 0,
|
|
84022
|
-
cells: [],
|
|
84023
|
-
cameras: []
|
|
84024
|
-
};
|
|
84025
|
-
/** A stored stats object, trusted only as far as its shape. */
|
|
84026
|
-
function readStats(value) {
|
|
84027
|
-
const raw = asRecord(parseJson(value));
|
|
84028
|
-
const num = (k) => typeof raw[k] === "number" ? raw[k] : 0;
|
|
84029
|
-
const cells = Array.isArray(raw["cells"]) ? raw["cells"] : [];
|
|
84030
|
-
const cameras = Array.isArray(raw["cameras"]) ? raw["cameras"] : [];
|
|
84031
|
-
return {
|
|
84032
|
-
...EMPTY_STATS,
|
|
84033
|
-
frames: num("frames"),
|
|
84034
|
-
completeFrames: num("completeFrames"),
|
|
84035
|
-
completeness: num("completeness"),
|
|
84036
|
-
subjects: num("subjects"),
|
|
84037
|
-
modelErrors: num("modelErrors"),
|
|
84038
|
-
hardNegatives: num("hardNegatives"),
|
|
84039
|
-
cells: cells.map((c) => {
|
|
84040
|
-
const r = asRecord(c);
|
|
84041
|
-
const lighting = r["lighting"] === "day" || r["lighting"] === "night" ? r["lighting"] : "unknown";
|
|
84042
|
-
return {
|
|
84043
|
-
macroClass: String(r["macroClass"] ?? ""),
|
|
84044
|
-
deviceId: Number(r["deviceId"] ?? 0),
|
|
84045
|
-
lighting,
|
|
84046
|
-
subjects: Number(r["subjects"] ?? 0),
|
|
84047
|
-
frames: Number(r["frames"] ?? 0)
|
|
84048
|
-
};
|
|
84049
|
-
}),
|
|
84050
|
-
cameras: cameras.map((c) => {
|
|
84051
|
-
const r = asRecord(c);
|
|
84052
|
-
return {
|
|
84053
|
-
deviceId: Number(r["deviceId"] ?? 0),
|
|
84054
|
-
frames: Number(r["frames"] ?? 0),
|
|
84055
|
-
completeFrames: Number(r["completeFrames"] ?? 0)
|
|
84056
|
-
};
|
|
84057
|
-
})
|
|
84058
|
-
};
|
|
84059
|
-
}
|
|
84060
|
-
var RetrainDatasetVersionStore = class {
|
|
84061
|
-
store;
|
|
84062
|
-
logger;
|
|
84301
|
+
//#region src/pipeline-analytics/retrain/retrain-dataset-version-routing.ts
|
|
84302
|
+
var RoutingDatasetVersionRepository = class {
|
|
84303
|
+
deps;
|
|
84304
|
+
lastPresence = false;
|
|
84305
|
+
migrated = false;
|
|
84306
|
+
migration = null;
|
|
84063
84307
|
constructor(deps) {
|
|
84064
|
-
this.
|
|
84065
|
-
this.logger = deps.logger;
|
|
84066
|
-
}
|
|
84067
|
-
static async declare(store) {
|
|
84068
|
-
await store.declareCollection.mutate({
|
|
84069
|
-
collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
|
|
84070
|
-
columns: [...COLUMNS],
|
|
84071
|
-
indexes: [...INDEXES]
|
|
84072
|
-
});
|
|
84308
|
+
this.deps = deps;
|
|
84073
84309
|
}
|
|
84074
84310
|
insert = async (version) => {
|
|
84075
|
-
await this.
|
|
84076
|
-
|
|
84077
|
-
|
|
84078
|
-
|
|
84079
|
-
|
|
84080
|
-
nameKey: nameKeyOf(version.name),
|
|
84081
|
-
createdAt: version.createdAt,
|
|
84082
|
-
selection: version.selection,
|
|
84083
|
-
layout: version.layout,
|
|
84084
|
-
completeOnly: version.completeOnly,
|
|
84085
|
-
frameIds: version.frameIds,
|
|
84086
|
-
annotationHash: version.annotationHash,
|
|
84087
|
-
manifestSchema: version.manifestSchema,
|
|
84088
|
-
stats: version.stats
|
|
84089
|
-
}
|
|
84090
|
-
});
|
|
84311
|
+
if (await this.route() === "local") {
|
|
84312
|
+
await this.deps.local.insert(version);
|
|
84313
|
+
return;
|
|
84314
|
+
}
|
|
84315
|
+
await this.deps.training.put(version);
|
|
84091
84316
|
};
|
|
84092
|
-
get = async (id) =>
|
|
84317
|
+
get = async (id) => await this.route() === "local" ? this.deps.local.get(id) : this.deps.training.get(id);
|
|
84318
|
+
list = async () => await this.route() === "local" ? this.deps.local.list() : this.deps.training.list();
|
|
84319
|
+
findIdByName = async (name) => await this.route() === "local" ? this.deps.local.findIdByName(name) : this.deps.training.findIdByName(name);
|
|
84320
|
+
remove = async (id) => await this.route() === "local" ? this.deps.local.remove(id) : this.deps.training.remove(id);
|
|
84321
|
+
async route() {
|
|
84322
|
+
if (!await this.presence()) return "local";
|
|
84323
|
+
if (this.migrated) return "training";
|
|
84324
|
+
return await this.migrateOnce() ? "training" : "local";
|
|
84325
|
+
}
|
|
84326
|
+
async presence() {
|
|
84327
|
+
let answer;
|
|
84093
84328
|
try {
|
|
84094
|
-
|
|
84095
|
-
|
|
84096
|
-
|
|
84097
|
-
});
|
|
84098
|
-
return row === null || row === void 0 ? null : this.toVersion(id, asRecord(row));
|
|
84099
|
-
} catch (err) {
|
|
84100
|
-
this.logger.warn("retrain dataset version read failed", { meta: {
|
|
84101
|
-
versionId: id,
|
|
84102
|
-
error: String(err)
|
|
84103
|
-
} });
|
|
84104
|
-
return null;
|
|
84329
|
+
answer = await this.deps.trainingPresent();
|
|
84330
|
+
} catch {
|
|
84331
|
+
answer = null;
|
|
84105
84332
|
}
|
|
84106
|
-
|
|
84107
|
-
|
|
84333
|
+
if (answer !== null) this.lastPresence = answer;
|
|
84334
|
+
return this.lastPresence;
|
|
84335
|
+
}
|
|
84336
|
+
migrateOnce() {
|
|
84337
|
+
if (this.migration === null) this.migration = this.migrate().finally(() => {
|
|
84338
|
+
this.migration = null;
|
|
84339
|
+
});
|
|
84340
|
+
return this.migration;
|
|
84341
|
+
}
|
|
84342
|
+
async migrate() {
|
|
84343
|
+
const { local, training, logger } = this.deps;
|
|
84108
84344
|
try {
|
|
84109
|
-
|
|
84110
|
-
|
|
84111
|
-
|
|
84112
|
-
|
|
84113
|
-
|
|
84114
|
-
|
|
84115
|
-
|
|
84345
|
+
const rows = await local.listRows();
|
|
84346
|
+
for (const row of rows) {
|
|
84347
|
+
await training.put(row);
|
|
84348
|
+
await local.remove(row.id);
|
|
84349
|
+
}
|
|
84350
|
+
this.migrated = true;
|
|
84351
|
+
if (rows.length > 0) logger.info("retrain dataset versions moved to the training capability", { meta: { versions: rows.length } });
|
|
84352
|
+
return true;
|
|
84116
84353
|
} catch (err) {
|
|
84117
|
-
|
|
84118
|
-
return
|
|
84354
|
+
logger.warn("retrain dataset versions could not move to training yet — kept local", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
84355
|
+
return false;
|
|
84119
84356
|
}
|
|
84120
|
-
};
|
|
84121
|
-
findIdByName = async (name) => {
|
|
84122
|
-
return (await this.store.query.query({
|
|
84123
|
-
collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
|
|
84124
|
-
filter: {
|
|
84125
|
-
where: { nameKey: nameKeyOf(name) },
|
|
84126
|
-
limit: 1
|
|
84127
|
-
}
|
|
84128
|
-
}))[0]?.id ?? null;
|
|
84129
|
-
};
|
|
84130
|
-
remove = async (id) => {
|
|
84131
|
-
if (await this.get(id) === null) return false;
|
|
84132
|
-
await this.store.delete.mutate({
|
|
84133
|
-
collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
|
|
84134
|
-
key: id
|
|
84135
|
-
});
|
|
84136
|
-
return true;
|
|
84137
|
-
};
|
|
84138
|
-
toVersion(id, data) {
|
|
84139
|
-
const selection = asRecord(parseJson(data["selection"]));
|
|
84140
|
-
const deviceIds = numberList(selection["deviceIds"]);
|
|
84141
|
-
const collectionIds = stringList(selection["collectionIds"]);
|
|
84142
|
-
const frameIdsSel = stringList(selection["frameIds"]);
|
|
84143
|
-
return {
|
|
84144
|
-
id,
|
|
84145
|
-
name: String(data["name"] ?? ""),
|
|
84146
|
-
createdAt: Number(data["createdAt"] ?? 0),
|
|
84147
|
-
selection: {
|
|
84148
|
-
...deviceIds !== void 0 ? { deviceIds } : {},
|
|
84149
|
-
...collectionIds !== void 0 ? { collectionIds } : {},
|
|
84150
|
-
...frameIdsSel !== void 0 ? { frameIds: frameIdsSel } : {}
|
|
84151
|
-
},
|
|
84152
|
-
layout: data["layout"] === "crops" ? "crops" : "frames",
|
|
84153
|
-
completeOnly: data["completeOnly"] === true || data["completeOnly"] === 1,
|
|
84154
|
-
frameIds: stringList(parseJson(data["frameIds"])) ?? [],
|
|
84155
|
-
annotationHash: String(data["annotationHash"] ?? ""),
|
|
84156
|
-
manifestSchema: String(data["manifestSchema"] ?? ""),
|
|
84157
|
-
stats: readStats(data["stats"])
|
|
84158
|
-
};
|
|
84159
84357
|
}
|
|
84160
84358
|
};
|
|
84161
84359
|
//#endregion
|
|
@@ -84825,6 +85023,61 @@ async function writeRetrainArchiveTo(deps) {
|
|
|
84825
85023
|
return stats;
|
|
84826
85024
|
}
|
|
84827
85025
|
//#endregion
|
|
85026
|
+
//#region src/pipeline-analytics/retrain/retrain-archive-offer.ts
|
|
85027
|
+
/** A reader silent this long is gone; the body is cut. */
|
|
85028
|
+
var RETRAIN_OFFER_STALL_MS = 6e4;
|
|
85029
|
+
function responseArchiveSink(res, stallMs = RETRAIN_OFFER_STALL_MS) {
|
|
85030
|
+
let closed = false;
|
|
85031
|
+
res.once("close", () => {
|
|
85032
|
+
closed = true;
|
|
85033
|
+
});
|
|
85034
|
+
return { write: (chunk) => new Promise((resolve, reject) => {
|
|
85035
|
+
if (closed || res.destroyed) {
|
|
85036
|
+
reject(/* @__PURE__ */ new Error("retrain archive reader went away"));
|
|
85037
|
+
return;
|
|
85038
|
+
}
|
|
85039
|
+
if (res.write(chunk)) {
|
|
85040
|
+
resolve();
|
|
85041
|
+
return;
|
|
85042
|
+
}
|
|
85043
|
+
const onDrain = () => {
|
|
85044
|
+
cleanup();
|
|
85045
|
+
resolve();
|
|
85046
|
+
};
|
|
85047
|
+
const onClose = () => {
|
|
85048
|
+
cleanup();
|
|
85049
|
+
reject(/* @__PURE__ */ new Error("retrain archive reader went away"));
|
|
85050
|
+
};
|
|
85051
|
+
const timer = setTimeout(() => {
|
|
85052
|
+
cleanup();
|
|
85053
|
+
res.destroy(/* @__PURE__ */ new Error("retrain archive reader stalled"));
|
|
85054
|
+
reject(/* @__PURE__ */ new Error(`retrain archive reader stalled for ${stallMs} ms`));
|
|
85055
|
+
}, stallMs);
|
|
85056
|
+
const cleanup = () => {
|
|
85057
|
+
clearTimeout(timer);
|
|
85058
|
+
res.off("drain", onDrain);
|
|
85059
|
+
res.off("close", onClose);
|
|
85060
|
+
};
|
|
85061
|
+
res.once("drain", onDrain);
|
|
85062
|
+
res.once("close", onClose);
|
|
85063
|
+
}) };
|
|
85064
|
+
}
|
|
85065
|
+
/**
|
|
85066
|
+
* Serve one archive: headers, the writer's chunks, the end. A writer that
|
|
85067
|
+
* throws destroys the response (the reader sees a truncated body, never a
|
|
85068
|
+
* clean end) and the error propagates to the caller's log.
|
|
85069
|
+
*/
|
|
85070
|
+
async function serveRetrainArchive(_req, res, writeArchive) {
|
|
85071
|
+
res.writeHead(200, { "content-type": "application/x-tar" });
|
|
85072
|
+
try {
|
|
85073
|
+
await writeArchive(responseArchiveSink(res));
|
|
85074
|
+
await new Promise((resolve) => res.end(resolve));
|
|
85075
|
+
} catch (err) {
|
|
85076
|
+
res.destroy(err instanceof Error ? err : new Error(String(err)));
|
|
85077
|
+
throw err;
|
|
85078
|
+
}
|
|
85079
|
+
}
|
|
85080
|
+
//#endregion
|
|
84828
85081
|
//#region src/pipeline-analytics/retrain/retrain-crop-cut.ts
|
|
84829
85082
|
/**
|
|
84830
85083
|
* The `crops` layout's cutter: one subject crop from a retrain frame copy.
|
|
@@ -111121,8 +111374,26 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
111121
111374
|
logger: logger.child("RetrainDatasets")
|
|
111122
111375
|
});
|
|
111123
111376
|
this.retrainDatasetVersions = retrainDatasetVersions;
|
|
111377
|
+
const routedDatasetVersions = new RoutingDatasetVersionRepository({
|
|
111378
|
+
local: retrainDatasetVersions,
|
|
111379
|
+
training: {
|
|
111380
|
+
put: (row) => api.training.putDatasetVersion.mutate({ ...row }),
|
|
111381
|
+
get: (id) => api.training.getDatasetVersion.query({ id }),
|
|
111382
|
+
list: async () => api.training.listDatasetVersions.query({}),
|
|
111383
|
+
findIdByName: async (name) => (await api.training.findDatasetVersionIdByName.query({ name })).id,
|
|
111384
|
+
remove: async (id) => (await api.training.removeDatasetVersion.mutate({ id })).removed
|
|
111385
|
+
},
|
|
111386
|
+
trainingPresent: async () => {
|
|
111387
|
+
try {
|
|
111388
|
+
return (await api.addons.listCapabilityProviders.query({ capName: "training" })).length > 0;
|
|
111389
|
+
} catch {
|
|
111390
|
+
return null;
|
|
111391
|
+
}
|
|
111392
|
+
},
|
|
111393
|
+
logger: logger.child("RetrainDatasets")
|
|
111394
|
+
});
|
|
111124
111395
|
this.retrainDatasets = new RetrainDatasetService({
|
|
111125
|
-
versions:
|
|
111396
|
+
versions: routedDatasetVersions,
|
|
111126
111397
|
buildPlan: (request) => this.buildRetrainExportPlan(request),
|
|
111127
111398
|
now: () => Date.now(),
|
|
111128
111399
|
newId: () => (0, node_crypto.randomUUID)(),
|
|
@@ -112487,6 +112758,51 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
112487
112758
|
}
|
|
112488
112759
|
}
|
|
112489
112760
|
/**
|
|
112761
|
+
* The same archive behind a one-shot `ctx.peerBytes` ticket (D805): the
|
|
112762
|
+
* caller (a training provider, same host) dials it at once and streams it
|
|
112763
|
+
* into its cloud. The plan is built now, so an empty or unknown selection
|
|
112764
|
+
* fails here rather than as an empty body; the bytes and their sha256 are
|
|
112765
|
+
* the reader's to measure.
|
|
112766
|
+
*/
|
|
112767
|
+
async offerRetrainArchive(input) {
|
|
112768
|
+
const frameStore = this.retrainFrames;
|
|
112769
|
+
if (frameStore === null) throw new Error("retrain export: analytics stores unavailable");
|
|
112770
|
+
const peerBytes = this.ctx.peerBytes;
|
|
112771
|
+
if (peerBytes === void 0) throw new Error("retrain archive offer: no peer-bytes facility");
|
|
112772
|
+
const plan = await this.buildRetrainExportPlan({
|
|
112773
|
+
filter: input.filter,
|
|
112774
|
+
layout: input.layout
|
|
112775
|
+
});
|
|
112776
|
+
if (plan.frames.length === 0) throw new Error("retrain archive offer: the selection holds no frame");
|
|
112777
|
+
const logger = this.ctx.logger;
|
|
112778
|
+
const offered = await peerBytes.offer({
|
|
112779
|
+
label: "retrain-archive",
|
|
112780
|
+
handler: (req, res) => serveRetrainArchive(req, res, async (sink) => {
|
|
112781
|
+
await writeRetrainArchiveTo({
|
|
112782
|
+
plan,
|
|
112783
|
+
readFrame: (frame) => frameStore.readFrame(frame),
|
|
112784
|
+
cutCrop: this.retrainCropCutter(),
|
|
112785
|
+
sink,
|
|
112786
|
+
markExported: (frameIds, batchId) => this.markRetrainExported(frameIds, batchId),
|
|
112787
|
+
logger,
|
|
112788
|
+
now: () => Date.now()
|
|
112789
|
+
});
|
|
112790
|
+
}).catch((err) => {
|
|
112791
|
+
logger.warn("retrain archive offer failed mid-body", { meta: { error: require_clip_model_registry.errMsg(err) } });
|
|
112792
|
+
})
|
|
112793
|
+
});
|
|
112794
|
+
if (offered.kind === "refused") throw new Error(`retrain archive offer refused: ${offered.code} — ${offered.detail}`);
|
|
112795
|
+
logger.info("retrain archive offered", { meta: {
|
|
112796
|
+
frames: plan.frames.length,
|
|
112797
|
+
layout: input.layout
|
|
112798
|
+
} });
|
|
112799
|
+
return {
|
|
112800
|
+
ticket: offered.ticket,
|
|
112801
|
+
frames: plan.frames.length,
|
|
112802
|
+
annotations: plan.manifest.annotationCount
|
|
112803
|
+
};
|
|
112804
|
+
}
|
|
112805
|
+
/**
|
|
112490
112806
|
* The store seam the `debug.*` actions and the debug bundle both read through.
|
|
112491
112807
|
*
|
|
112492
112808
|
* Index reads, plus ONE existence probe per media row — never a blob. The
|