@camstack/addon-post-analysis 1.2.234 → 1.2.237
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{dist-Du1qDTXV.mjs → dist-BDpucx_W.mjs} +202 -9
- package/dist/{dist-DTRcLvW5.js → dist-IoI_ufBo.js} +202 -9
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D-bBcfRY.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DIQIycqg.mjs} +3 -3
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-C5YuStaN.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dl-N-7EJ.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-C9lFCeZi.mjs → hostInit-B5V7K95U.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +494 -29
- package/dist/pipeline-analytics/index.mjs +494 -29
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-IoI_ufBo.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -7445,6 +7445,16 @@ var NcAlertStore = class {
|
|
|
7445
7445
|
};
|
|
7446
7446
|
}
|
|
7447
7447
|
};
|
|
7448
|
+
function deriveConsumableBand(input) {
|
|
7449
|
+
const { level, status } = input.reading;
|
|
7450
|
+
const measured = level !== null && Number.isFinite(level);
|
|
7451
|
+
if (!measured && status === null) return null;
|
|
7452
|
+
if (status === "replace") return "low";
|
|
7453
|
+
if (!measured) return "normal";
|
|
7454
|
+
const value = level;
|
|
7455
|
+
if (input.previous === "low") return value >= input.thresholdPercent + 5 ? "normal" : "low";
|
|
7456
|
+
return value <= input.thresholdPercent ? "low" : "normal";
|
|
7457
|
+
}
|
|
7448
7458
|
//#endregion
|
|
7449
7459
|
//#region src/notification-center/device-directory.ts
|
|
7450
7460
|
/**
|
|
@@ -9845,6 +9855,40 @@ function incomingFromBatteryBand(input) {
|
|
|
9845
9855
|
});
|
|
9846
9856
|
}
|
|
9847
9857
|
/**
|
|
9858
|
+
* A consumable BAND, as a system-event subject.
|
|
9859
|
+
*
|
|
9860
|
+
* Built exactly like {@link incomingFromBatteryBand} — the operator asked for
|
|
9861
|
+
* these to be treated as the battery notifications are — with two differences
|
|
9862
|
+
* that come from the subject and not from taste:
|
|
9863
|
+
*
|
|
9864
|
+
* - **the subject carries the ITEM.** `consumable:<deviceId>:<key>`, because a
|
|
9865
|
+
* vacuum has a brush AND a filter AND a mop pad, and one subject for all
|
|
9866
|
+
* three would let the brush's alert cancel the filter's.
|
|
9867
|
+
* - **the body variant follows what is KNOWN.** An item with no percentage
|
|
9868
|
+
* gets a finished sentence rather than a template with a stand-in number in
|
|
9869
|
+
* it, for the same reason a binary battery does.
|
|
9870
|
+
*
|
|
9871
|
+
* The recovery keeps the SAME subject: it is the same episode ending.
|
|
9872
|
+
*/
|
|
9873
|
+
function incomingFromConsumableBand(input) {
|
|
9874
|
+
const kind = input.band === "low" ? "device-consumable-low" : "device-consumable-normal";
|
|
9875
|
+
const measured = input.item.level !== null && Number.isFinite(input.item.level);
|
|
9876
|
+
return systemSubject({
|
|
9877
|
+
facts: input.facts,
|
|
9878
|
+
kind,
|
|
9879
|
+
subject: `consumable:${input.deviceId}:${input.item.key}`,
|
|
9880
|
+
titleKey: `system.${kind}.title`,
|
|
9881
|
+
bodyKey: measured ? `system.${kind}.body` : `system.${kind}.body.unmeasured`,
|
|
9882
|
+
textParams: {
|
|
9883
|
+
...deviceTextParams(input.deviceId, input.identity),
|
|
9884
|
+
item: input.item.label,
|
|
9885
|
+
...measured ? { percentage: String(Math.round(input.item.level)) } : {}
|
|
9886
|
+
},
|
|
9887
|
+
deviceId: input.deviceId,
|
|
9888
|
+
...input.identity !== void 0 ? { deviceType: input.identity.type } : {}
|
|
9889
|
+
});
|
|
9890
|
+
}
|
|
9891
|
+
/**
|
|
9848
9892
|
* Normalize the infrastructure categories consumed by Notification Center.
|
|
9849
9893
|
* Malformed payloads are rejected instead of guessing an identity.
|
|
9850
9894
|
*
|
|
@@ -9996,6 +10040,7 @@ function incomingFromSystemEvent(event, lookup) {
|
|
|
9996
10040
|
}
|
|
9997
10041
|
case "system.restart-completed": return incomingFromServerUpdated(facts, data);
|
|
9998
10042
|
case "recording.export.completed": return incomingFromExportCompleted(facts, data);
|
|
10043
|
+
case "backup.run-completed": return incomingFromBackupCompleted(facts, data);
|
|
9999
10044
|
default: return null;
|
|
10000
10045
|
}
|
|
10001
10046
|
}
|
|
@@ -10159,6 +10204,37 @@ function incomingFromServerUpdated(facts, data) {
|
|
|
10159
10204
|
nodeIds: [nodeId]
|
|
10160
10205
|
});
|
|
10161
10206
|
}
|
|
10207
|
+
/**
|
|
10208
|
+
* A finished backup RUN.
|
|
10209
|
+
*
|
|
10210
|
+
* Subject is the run, not the destination: `backup.completed` fires once per
|
|
10211
|
+
* destination and an operator with three of them would be told three times.
|
|
10212
|
+
* The counts travel in the text because a PARTIAL run must not read as a clean
|
|
10213
|
+
* one — two destinations of three is news, and a bare "backup completed" would
|
|
10214
|
+
* hide it.
|
|
10215
|
+
*/
|
|
10216
|
+
function incomingFromBackupCompleted(facts, data) {
|
|
10217
|
+
const ok = numberField(data, "destinationsOk");
|
|
10218
|
+
const total = numberField(data, "destinationsTotal");
|
|
10219
|
+
if (ok === void 0 || total === void 0) return null;
|
|
10220
|
+
const failed = numberField(data, "destinationsFailed") ?? Math.max(0, total - ok);
|
|
10221
|
+
const sizeMB = numberField(data, "sizeMB");
|
|
10222
|
+
const backupId = stringField(data, "backupId");
|
|
10223
|
+
return systemSubject({
|
|
10224
|
+
facts,
|
|
10225
|
+
kind: "backup-completed",
|
|
10226
|
+
subject: `backup:${backupId ?? facts.timestamp}`,
|
|
10227
|
+
titleKey: "system.backup-completed.title",
|
|
10228
|
+
bodyKey: failed > 0 ? "system.backup-completed.body.partial" : "system.backup-completed.body",
|
|
10229
|
+
textParams: {
|
|
10230
|
+
destinationsOk: String(ok),
|
|
10231
|
+
destinationsTotal: String(total),
|
|
10232
|
+
destinationsFailed: String(failed),
|
|
10233
|
+
...sizeMB !== void 0 ? { sizeMB: String(sizeMB) } : {},
|
|
10234
|
+
...backupId !== void 0 ? { backupId } : {}
|
|
10235
|
+
}
|
|
10236
|
+
});
|
|
10237
|
+
}
|
|
10162
10238
|
function incomingFromExportCompleted(facts, data) {
|
|
10163
10239
|
const exportId = stringField(data, "exportId");
|
|
10164
10240
|
if (exportId === void 0) return null;
|
|
@@ -10496,6 +10572,8 @@ var NcDeviceAlerts = class {
|
|
|
10496
10572
|
* durable.
|
|
10497
10573
|
*/
|
|
10498
10574
|
lastReading = /* @__PURE__ */ new Map();
|
|
10575
|
+
/** The last reading per consumable SUBJECT, for the reminder sweep. */
|
|
10576
|
+
lastConsumable = /* @__PURE__ */ new Map();
|
|
10499
10577
|
constructor(deps) {
|
|
10500
10578
|
this.deps = deps;
|
|
10501
10579
|
this.now = deps.now ?? Date.now;
|
|
@@ -10533,6 +10611,44 @@ var NcDeviceAlerts = class {
|
|
|
10533
10611
|
}));
|
|
10534
10612
|
}
|
|
10535
10613
|
/**
|
|
10614
|
+
* ONE consumable item, from a device's `consumables` slice.
|
|
10615
|
+
*
|
|
10616
|
+
* Per ITEM, never per device: a vacuum has a brush, a filter and a mop pad,
|
|
10617
|
+
* and folding them into one subject would make the brush's alert cancel the
|
|
10618
|
+
* filter's — the same collision D141 split `device-switch:<id>` off liveness
|
|
10619
|
+
* to avoid. Each item is its own episode, its own cooldown, its own recovery.
|
|
10620
|
+
*
|
|
10621
|
+
* An item that has told us nothing leaves EVERYTHING untouched, for the
|
|
10622
|
+
* reason `observeBattery` does: seeding it healthy is a claim, and the first
|
|
10623
|
+
* genuine `replace` would then read as a transition out of a state nobody
|
|
10624
|
+
* observed.
|
|
10625
|
+
*/
|
|
10626
|
+
observeConsumable(deviceId, item) {
|
|
10627
|
+
const subject = `consumable:${deviceId}:${item.key}`;
|
|
10628
|
+
const previous = this.deps.rows.get(subject);
|
|
10629
|
+
const settings = this.deps.settings();
|
|
10630
|
+
const band = deriveConsumableBand({
|
|
10631
|
+
reading: {
|
|
10632
|
+
level: item.level,
|
|
10633
|
+
status: item.status
|
|
10634
|
+
},
|
|
10635
|
+
thresholdPercent: settings.consumableLowPercent,
|
|
10636
|
+
previous: previous?.alerting === true ? "low" : previous === void 0 ? void 0 : "normal"
|
|
10637
|
+
});
|
|
10638
|
+
if (band === null) return;
|
|
10639
|
+
this.lastConsumable.set(subject, {
|
|
10640
|
+
deviceId,
|
|
10641
|
+
item
|
|
10642
|
+
});
|
|
10643
|
+
this.apply(subject, band === "low", settings, (facts, alerting) => incomingFromConsumableBand({
|
|
10644
|
+
facts,
|
|
10645
|
+
deviceId,
|
|
10646
|
+
band: alerting ? "low" : "normal",
|
|
10647
|
+
item,
|
|
10648
|
+
identity: this.deps.lookup(deviceId)
|
|
10649
|
+
}));
|
|
10650
|
+
}
|
|
10651
|
+
/**
|
|
10536
10652
|
* The reminder sweep. Called from an existing periodic tick; it allocates
|
|
10537
10653
|
* nothing on the healthy path.
|
|
10538
10654
|
*
|
|
@@ -10546,6 +10662,17 @@ var NcDeviceAlerts = class {
|
|
|
10546
10662
|
if (settings.renotifyMinutes <= 0) return;
|
|
10547
10663
|
for (const row of this.deps.rows.all()) {
|
|
10548
10664
|
if (!row.alerting) continue;
|
|
10665
|
+
const consumable = this.lastConsumable.get(row.subject);
|
|
10666
|
+
if (consumable !== void 0) {
|
|
10667
|
+
this.apply(row.subject, true, settings, (facts) => incomingFromConsumableBand({
|
|
10668
|
+
facts,
|
|
10669
|
+
deviceId: consumable.deviceId,
|
|
10670
|
+
band: "low",
|
|
10671
|
+
item: consumable.item,
|
|
10672
|
+
identity: this.deps.lookup(consumable.deviceId)
|
|
10673
|
+
}));
|
|
10674
|
+
continue;
|
|
10675
|
+
}
|
|
10549
10676
|
const restatement = this.lastReading.get(row.subject);
|
|
10550
10677
|
if (restatement === void 0) continue;
|
|
10551
10678
|
this.apply(row.subject, true, settings, (facts) => incomingFromBatteryBand({
|
|
@@ -11654,6 +11781,15 @@ var en_default = {
|
|
|
11654
11781
|
"system.export-completed.title": "Export ready",
|
|
11655
11782
|
"system.export-completed.body": "The footage export is ready",
|
|
11656
11783
|
"system.export-completed.body.titled": "{{title}} is ready",
|
|
11784
|
+
"system.backup-completed.title": "Backup completed",
|
|
11785
|
+
"system.backup-completed.body": "Backup saved to {{destinationsOk}} of {{destinationsTotal}} destinations ({{sizeMB}} MB)",
|
|
11786
|
+
"system.backup-completed.body.partial": "Backup saved to {{destinationsOk}} of {{destinationsTotal}} destinations — {{destinationsFailed}} failed",
|
|
11787
|
+
"system.device-consumable-low.title": "Consumable low",
|
|
11788
|
+
"system.device-consumable-low.body": "{{item}} on {{deviceName}} is at {{percentage}}%",
|
|
11789
|
+
"system.device-consumable-low.body.unmeasured": "{{item}} on {{deviceName}} needs replacing",
|
|
11790
|
+
"system.device-consumable-normal.title": "Consumable replaced",
|
|
11791
|
+
"system.device-consumable-normal.body": "{{item}} on {{deviceName}} is back to {{percentage}}%",
|
|
11792
|
+
"system.device-consumable-normal.body.unmeasured": "{{item}} on {{deviceName}} is OK again",
|
|
11657
11793
|
"system.camera-online.title": "Device online",
|
|
11658
11794
|
"system.camera-online.body": "Device {{deviceName}} is online",
|
|
11659
11795
|
"system.camera-offline.title": "Device offline",
|
|
@@ -11914,6 +12050,15 @@ var it_default = {
|
|
|
11914
12050
|
"system.export-completed.title": "Export pronto",
|
|
11915
12051
|
"system.export-completed.body": "L'export del filmato è pronto",
|
|
11916
12052
|
"system.export-completed.body.titled": "{{title}} è pronto",
|
|
12053
|
+
"system.backup-completed.title": "Backup completato",
|
|
12054
|
+
"system.backup-completed.body": "Backup salvato su {{destinationsOk}} destinazioni su {{destinationsTotal}} ({{sizeMB}} MB)",
|
|
12055
|
+
"system.backup-completed.body.partial": "Backup salvato su {{destinationsOk}} destinazioni su {{destinationsTotal}} — {{destinationsFailed}} fallite",
|
|
12056
|
+
"system.device-consumable-low.title": "Consumabile quasi esaurito",
|
|
12057
|
+
"system.device-consumable-low.body": "{{item}} su {{deviceName}} è al {{percentage}}%",
|
|
12058
|
+
"system.device-consumable-low.body.unmeasured": "{{item}} su {{deviceName}} va sostituito",
|
|
12059
|
+
"system.device-consumable-normal.title": "Consumabile sostituito",
|
|
12060
|
+
"system.device-consumable-normal.body": "{{item}} su {{deviceName}} è tornato al {{percentage}}%",
|
|
12061
|
+
"system.device-consumable-normal.body.unmeasured": "{{item}} su {{deviceName}} è di nuovo a posto",
|
|
11917
12062
|
"system.camera-online.title": "Dispositivo online",
|
|
11918
12063
|
"system.camera-online.body": "Il dispositivo {{deviceName}} è online",
|
|
11919
12064
|
"system.camera-offline.title": "Dispositivo offline",
|
|
@@ -22237,6 +22382,7 @@ function alarmButtonKind(kind) {
|
|
|
22237
22382
|
*/
|
|
22238
22383
|
var NC_DEVICE_ALERT_FALLBACK = {
|
|
22239
22384
|
batteryLowPercent: 20,
|
|
22385
|
+
consumableLowPercent: 15,
|
|
22240
22386
|
renotifyMinutes: 720,
|
|
22241
22387
|
notifyRecovery: true
|
|
22242
22388
|
};
|
|
@@ -22269,6 +22415,52 @@ function batterySliceOf(event) {
|
|
|
22269
22415
|
};
|
|
22270
22416
|
}
|
|
22271
22417
|
/**
|
|
22418
|
+
* A `device.state-changed` carrying a CONSUMABLES slice, reduced to the items
|
|
22419
|
+
* the band derivation reads.
|
|
22420
|
+
*
|
|
22421
|
+
* Same tap as the battery's: `DeviceStateChanged` is already subscribed, so
|
|
22422
|
+
* every `consumables` write already arrives here and was simply dropped. No
|
|
22423
|
+
* new bus category and no producer change.
|
|
22424
|
+
*
|
|
22425
|
+
* An item is kept only when it carries a usable `key` — the subject is built
|
|
22426
|
+
* from it, and an item with no key could not be told apart from its sibling.
|
|
22427
|
+
* `level` and `status` are each optional; an item with NEITHER is still
|
|
22428
|
+
* returned, because deciding that it has told us nothing belongs to
|
|
22429
|
+
* `deriveConsumableBand` and not to a parser (D393: absent is not zero, and
|
|
22430
|
+
* the parser is not the place that judges).
|
|
22431
|
+
*/
|
|
22432
|
+
function consumablesSliceOf(event) {
|
|
22433
|
+
if (event.category !== "device.state-changed") return null;
|
|
22434
|
+
const data = event.data;
|
|
22435
|
+
if (data["capName"] !== "consumables") return null;
|
|
22436
|
+
const deviceId = data["deviceId"];
|
|
22437
|
+
const slice = data["slice"];
|
|
22438
|
+
if (typeof deviceId !== "number") return null;
|
|
22439
|
+
if (typeof slice !== "object" || slice === null || Array.isArray(slice)) return null;
|
|
22440
|
+
const rawItems = slice["items"];
|
|
22441
|
+
if (!Array.isArray(rawItems)) return null;
|
|
22442
|
+
const items = [];
|
|
22443
|
+
for (const entry of rawItems) {
|
|
22444
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
|
|
22445
|
+
const row = entry;
|
|
22446
|
+
const key = row["key"];
|
|
22447
|
+
if (typeof key !== "string" || key.length === 0) continue;
|
|
22448
|
+
const level = row["level"];
|
|
22449
|
+
const status = row["status"];
|
|
22450
|
+
const label = row["label"];
|
|
22451
|
+
items.push({
|
|
22452
|
+
key,
|
|
22453
|
+
label: typeof label === "string" && label.length > 0 ? label : key,
|
|
22454
|
+
level: typeof level === "number" ? level : null,
|
|
22455
|
+
status: status === "ok" || status === "replace" ? status : null
|
|
22456
|
+
});
|
|
22457
|
+
}
|
|
22458
|
+
return items.length === 0 ? null : {
|
|
22459
|
+
deviceId,
|
|
22460
|
+
items
|
|
22461
|
+
};
|
|
22462
|
+
}
|
|
22463
|
+
/**
|
|
22272
22464
|
* What `getAlarmConfig` answers on a node with no panel.
|
|
22273
22465
|
*
|
|
22274
22466
|
* The DEFAULTS, not zeroes: paired with `deviceId: null` this tells a client
|
|
@@ -24317,6 +24509,11 @@ var NotificationCenter = class NotificationCenter {
|
|
|
24317
24509
|
this.deviceAlerts.observeBattery(battery.deviceId, battery.reading);
|
|
24318
24510
|
return;
|
|
24319
24511
|
}
|
|
24512
|
+
const consumables = consumablesSliceOf(event);
|
|
24513
|
+
if (consumables !== null) {
|
|
24514
|
+
for (const item of consumables.items) this.deviceAlerts.observeConsumable(consumables.deviceId, item);
|
|
24515
|
+
return;
|
|
24516
|
+
}
|
|
24320
24517
|
const lookup = this.deviceDirectory.lookup();
|
|
24321
24518
|
const incoming = incomingFromSystemEvent(event, lookup);
|
|
24322
24519
|
if (incoming === null) {
|
|
@@ -25929,6 +26126,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
25929
26126
|
deviceAlertSettings() {
|
|
25930
26127
|
return this.deps.readDeviceAlertSettings?.() ?? {
|
|
25931
26128
|
batteryLowPercent: NC_DEVICE_ALERT_FALLBACK.batteryLowPercent,
|
|
26129
|
+
consumableLowPercent: NC_DEVICE_ALERT_FALLBACK.consumableLowPercent,
|
|
25932
26130
|
renotifyMinutes: NC_DEVICE_ALERT_FALLBACK.renotifyMinutes,
|
|
25933
26131
|
notifyRecovery: NC_DEVICE_ALERT_FALLBACK.notifyRecovery
|
|
25934
26132
|
};
|
|
@@ -26544,6 +26742,7 @@ function systemEventSubject(kind, scope) {
|
|
|
26544
26742
|
if (kind === "addon-updated") return `packages:addon-updated:${scope.packageName ?? "pkg"}`;
|
|
26545
26743
|
if (kind === "server-updated") return `server-updated:${scope.nodeId ?? "hub"}`;
|
|
26546
26744
|
if (kind === "export-completed") return `export:${scope.deviceId ?? 0}`;
|
|
26745
|
+
if (kind === "backup-completed") return "backup:test";
|
|
26547
26746
|
return `alarm:${scope.deviceId ?? 0}`;
|
|
26548
26747
|
}
|
|
26549
26748
|
/**
|
|
@@ -31654,7 +31853,8 @@ function analyseDebugTrack(inventory) {
|
|
|
31654
31853
|
}
|
|
31655
31854
|
}
|
|
31656
31855
|
if (closed && facts.bestEventId === void 0) out.push(finding("NO_BEST_EVENT", "info", "No representative event was chosen for this track."));
|
|
31657
|
-
if (facts.
|
|
31856
|
+
if (facts.labelStepId === "detector:raw-class") out.push(finding("UNSCORED_LABEL", "info", `The label is the detector's raw class (${facts.label ?? facts.className}) — no scored classifier answered for this track.`));
|
|
31857
|
+
else if (facts.labelScore !== void 0 && facts.labelScore < .5) out.push(finding("LOW_BEST_SCORE", "info", `Best label score ${facts.labelScore.toFixed(2)} is under ${String(LOW_BEST_SCORE)}.`));
|
|
31658
31858
|
if (thumbnailProvenance === "standin") out.push(finding("BEST_SHOT_STANDIN", "warn", "Best shot is a stand-in: a face/plate detail crop copied in because no frame of the subject was captured."));
|
|
31659
31859
|
if (thumbnailProvenance === "raster") out.push(finding("BEST_SHOT_RASTER", "info", "Best shot is a sub-native raster crop of the subject, not a native capture."));
|
|
31660
31860
|
return out;
|
|
@@ -31712,6 +31912,7 @@ function toFacts(track) {
|
|
|
31712
31912
|
className: track.className,
|
|
31713
31913
|
...track.label === void 0 ? {} : { label: track.label },
|
|
31714
31914
|
...track.labelScore === void 0 ? {} : { labelScore: track.labelScore },
|
|
31915
|
+
...track.labelMeta?.stepId === void 0 ? {} : { labelStepId: track.labelMeta.stepId },
|
|
31715
31916
|
firstSeen: track.firstSeen,
|
|
31716
31917
|
lastSeen: track.lastSeen,
|
|
31717
31918
|
active: track.active,
|
|
@@ -47382,31 +47583,54 @@ var EventMediaService = class {
|
|
|
47382
47583
|
//#endregion
|
|
47383
47584
|
//#region src/shared/frame/crop-extractor.ts
|
|
47384
47585
|
/**
|
|
47385
|
-
*
|
|
47386
|
-
*
|
|
47586
|
+
* The ONE reading of a normalized box against a frame: round, then clamp so
|
|
47587
|
+
* the rectangle is inside the frame and never empty. Both extractors below
|
|
47588
|
+
* share it, so the JPEG half and the raw half can never disagree about WHICH
|
|
47589
|
+
* pixels the crop is — only about how they are encoded.
|
|
47590
|
+
*/
|
|
47591
|
+
function clampNormalizedBox(frameWidth, frameHeight, bbox) {
|
|
47592
|
+
const left = Math.max(0, Math.min(Math.round(bbox.x * frameWidth), frameWidth - 1));
|
|
47593
|
+
const top = Math.max(0, Math.min(Math.round(bbox.y * frameHeight), frameHeight - 1));
|
|
47594
|
+
return {
|
|
47595
|
+
left,
|
|
47596
|
+
top,
|
|
47597
|
+
width: Math.max(1, Math.min(Math.round(bbox.w * frameWidth), frameWidth - left)),
|
|
47598
|
+
height: Math.max(1, Math.min(Math.round(bbox.h * frameHeight), frameHeight - top))
|
|
47599
|
+
};
|
|
47600
|
+
}
|
|
47601
|
+
/**
|
|
47602
|
+
* Extracts a **JPEG-encoded** crop from a raw frame buffer using a normalized
|
|
47603
|
+
* bounding box. The name does not say JPEG; the return type is what you must
|
|
47604
|
+
* read. A caller whose surrounding frame is raw wants {@link extractCropRaw} —
|
|
47605
|
+
* handing this output on as raw pixels is what produced 717 `VipsImage: memory
|
|
47606
|
+
* area too small` failures and cost the fleet a day of group shots.
|
|
47387
47607
|
*/
|
|
47388
47608
|
async function extractCrop(frameData, frameWidth, frameHeight, bbox) {
|
|
47389
|
-
const
|
|
47390
|
-
const rawTop = Math.round(bbox.y * frameHeight);
|
|
47391
|
-
const rawWidth = Math.round(bbox.w * frameWidth);
|
|
47392
|
-
const rawHeight = Math.round(bbox.h * frameHeight);
|
|
47393
|
-
const left = Math.max(0, Math.min(rawLeft, frameWidth - 1));
|
|
47394
|
-
const top = Math.max(0, Math.min(rawTop, frameHeight - 1));
|
|
47395
|
-
const width = Math.max(1, Math.min(rawWidth, frameWidth - left));
|
|
47396
|
-
const height = Math.max(1, Math.min(rawHeight, frameHeight - top));
|
|
47609
|
+
const rect = clampNormalizedBox(frameWidth, frameHeight, bbox);
|
|
47397
47610
|
return {
|
|
47398
47611
|
crop: await (0, sharp.default)(frameData, { raw: {
|
|
47399
47612
|
width: frameWidth,
|
|
47400
47613
|
height: frameHeight,
|
|
47401
47614
|
channels: 3
|
|
47402
|
-
} }).extract({
|
|
47403
|
-
|
|
47404
|
-
|
|
47405
|
-
|
|
47406
|
-
|
|
47407
|
-
|
|
47408
|
-
|
|
47409
|
-
|
|
47615
|
+
} }).extract(rect).jpeg({ quality: 90 }).toBuffer(),
|
|
47616
|
+
width: rect.width,
|
|
47617
|
+
height: rect.height
|
|
47618
|
+
};
|
|
47619
|
+
}
|
|
47620
|
+
/**
|
|
47621
|
+
* The same cut, raw in and **raw RGB out** — for a caller that carries a
|
|
47622
|
+
* `DecodedFrame` and must keep `format: 'rgb'` true of the bytes it holds.
|
|
47623
|
+
*/
|
|
47624
|
+
async function extractCropRaw(frameData, frameWidth, frameHeight, bbox) {
|
|
47625
|
+
const rect = clampNormalizedBox(frameWidth, frameHeight, bbox);
|
|
47626
|
+
return {
|
|
47627
|
+
data: await (0, sharp.default)(frameData, { raw: {
|
|
47628
|
+
width: frameWidth,
|
|
47629
|
+
height: frameHeight,
|
|
47630
|
+
channels: 3
|
|
47631
|
+
} }).extract(rect).raw().toBuffer(),
|
|
47632
|
+
width: rect.width,
|
|
47633
|
+
height: rect.height
|
|
47410
47634
|
};
|
|
47411
47635
|
}
|
|
47412
47636
|
//#endregion
|
|
@@ -49947,6 +50171,19 @@ var DeviceAlertSettingsSchema = require_dist.object({
|
|
|
49947
50171
|
*/
|
|
49948
50172
|
batteryLowPercent: require_dist.number().int().min(1).max(99).default(20),
|
|
49949
50173
|
/**
|
|
50174
|
+
* What counts as a nearly-exhausted CONSUMABLE — a filter, a brush, a mop pad.
|
|
50175
|
+
*
|
|
50176
|
+
* Its own number and not the battery's, because the two are different chores
|
|
50177
|
+
* on different clocks: a battery at 20 % is hours from flat, a brush at 20 %
|
|
50178
|
+
* is weeks from worn, and an operator who wants to be told early about one
|
|
50179
|
+
* does not necessarily want to be told early about the other.
|
|
50180
|
+
*
|
|
50181
|
+
* Applies to the MEASURED side only. An item whose provider states a
|
|
50182
|
+
* `replace` verdict is low whatever this says — the provider decided, and a
|
|
50183
|
+
* threshold cannot talk it out of it (`alerts/consumable-band.ts`).
|
|
50184
|
+
*/
|
|
50185
|
+
consumableLowPercent: require_dist.number().int().min(1).max(99).default(15),
|
|
50186
|
+
/**
|
|
49950
50187
|
* How long a still-true alert waits before it says itself again.
|
|
49951
50188
|
*
|
|
49952
50189
|
* **0 = say it once.** That is the behaviour of every other notification this
|
|
@@ -58592,7 +58829,9 @@ function stationaryEvidenceKey(deviceId, className, anchorId) {
|
|
|
58592
58829
|
* refused is the FIRST thing it failed, not the last.
|
|
58593
58830
|
*/
|
|
58594
58831
|
function evaluateStationaryEvidence(row, policy, classEnabled) {
|
|
58595
|
-
const
|
|
58832
|
+
const windowSeen = row.windowSeen + row.prevWindowSeen;
|
|
58833
|
+
const windowAgreed = row.windowAgreed + row.prevWindowAgreed;
|
|
58834
|
+
const ratio = windowSeen > 0 ? windowAgreed / windowSeen : 0;
|
|
58596
58835
|
const spanMs = row.lastAgreedAt - row.firstAgreedAt;
|
|
58597
58836
|
const base = {
|
|
58598
58837
|
agreed: row.agreed,
|
|
@@ -58626,6 +58865,36 @@ function evaluateStationaryEvidence(row, policy, classEnabled) {
|
|
|
58626
58865
|
};
|
|
58627
58866
|
}
|
|
58628
58867
|
/**
|
|
58868
|
+
* Slide the window forward to `now`.
|
|
58869
|
+
*
|
|
58870
|
+
* One bucket width past: the current bucket becomes the previous one and a
|
|
58871
|
+
* fresh one opens. TWO or more past: the candidate was not observed for the
|
|
58872
|
+
* whole window, so both buckets are empty — and an empty window answers `0`,
|
|
58873
|
+
* which is an honest "nothing recent to judge by" rather than a stale verdict.
|
|
58874
|
+
* The lifetime counters are untouched, so nothing a candidate has earned is
|
|
58875
|
+
* lost — only its claim about the present expires.
|
|
58876
|
+
*/
|
|
58877
|
+
function rotateWindow(row, now) {
|
|
58878
|
+
const elapsed = now - row.windowStartedAt;
|
|
58879
|
+
if (elapsed < 9e5) return row;
|
|
58880
|
+
if (elapsed < 2 * 9e5) return {
|
|
58881
|
+
...row,
|
|
58882
|
+
prevWindowAgreed: row.windowAgreed,
|
|
58883
|
+
prevWindowSeen: row.windowSeen,
|
|
58884
|
+
windowAgreed: 0,
|
|
58885
|
+
windowSeen: 0,
|
|
58886
|
+
windowStartedAt: now
|
|
58887
|
+
};
|
|
58888
|
+
return {
|
|
58889
|
+
...row,
|
|
58890
|
+
prevWindowAgreed: 0,
|
|
58891
|
+
prevWindowSeen: 0,
|
|
58892
|
+
windowAgreed: 0,
|
|
58893
|
+
windowSeen: 0,
|
|
58894
|
+
windowStartedAt: now
|
|
58895
|
+
};
|
|
58896
|
+
}
|
|
58897
|
+
/**
|
|
58629
58898
|
* The candidate this observation belongs to: the same-class row whose anchor it
|
|
58630
58899
|
* overlaps most, provided the overlap reaches `agreeIou`. Overlap is the ONLY
|
|
58631
58900
|
* notion of sameness here, and it is the same {@link iou} the suppression gate
|
|
@@ -58659,15 +58928,19 @@ function matchEvidenceCandidate(rows, className, bbox, agreeIou) {
|
|
|
58659
58928
|
*/
|
|
58660
58929
|
function foldObservation(row, observation, agrees, overlap) {
|
|
58661
58930
|
const seen = row.seen + 1;
|
|
58931
|
+
const rotated = rotateWindow(row, observation.timestamp);
|
|
58662
58932
|
if (!agrees) return {
|
|
58663
|
-
...
|
|
58933
|
+
...rotated,
|
|
58664
58934
|
seen,
|
|
58935
|
+
windowSeen: rotated.windowSeen + 1,
|
|
58665
58936
|
updatedAt: observation.timestamp
|
|
58666
58937
|
};
|
|
58667
58938
|
const improves = overlap >= iou(row.anchor, row.bestBbox);
|
|
58668
58939
|
return {
|
|
58669
|
-
...
|
|
58940
|
+
...rotated,
|
|
58670
58941
|
seen,
|
|
58942
|
+
windowSeen: rotated.windowSeen + 1,
|
|
58943
|
+
windowAgreed: rotated.windowAgreed + 1,
|
|
58671
58944
|
agreed: row.agreed + 1,
|
|
58672
58945
|
firstAgreedAt: Math.min(row.firstAgreedAt, observation.timestamp),
|
|
58673
58946
|
lastAgreedAt: Math.max(row.lastAgreedAt, observation.timestamp),
|
|
@@ -58691,6 +58964,11 @@ function newEvidenceRow(observation, anchorId) {
|
|
|
58691
58964
|
anchor: { ...observation.bbox },
|
|
58692
58965
|
agreed: 1,
|
|
58693
58966
|
seen: 1,
|
|
58967
|
+
windowAgreed: 1,
|
|
58968
|
+
windowSeen: 1,
|
|
58969
|
+
prevWindowAgreed: 0,
|
|
58970
|
+
prevWindowSeen: 0,
|
|
58971
|
+
windowStartedAt: observation.timestamp,
|
|
58694
58972
|
firstAgreedAt: observation.timestamp,
|
|
58695
58973
|
lastAgreedAt: observation.timestamp,
|
|
58696
58974
|
bestBbox: { ...observation.bbox },
|
|
@@ -58829,6 +59107,11 @@ var StationaryEvidenceLedger = class {
|
|
|
58829
59107
|
anchor: { ...row.anchor },
|
|
58830
59108
|
agreed: row.agreed,
|
|
58831
59109
|
seen: row.seen,
|
|
59110
|
+
windowAgreed: row.windowAgreed,
|
|
59111
|
+
windowSeen: row.windowSeen,
|
|
59112
|
+
prevWindowAgreed: row.prevWindowAgreed,
|
|
59113
|
+
prevWindowSeen: row.prevWindowSeen,
|
|
59114
|
+
windowStartedAt: row.windowStartedAt,
|
|
58832
59115
|
firstAgreedAt: row.firstAgreedAt,
|
|
58833
59116
|
lastAgreedAt: row.lastAgreedAt,
|
|
58834
59117
|
bestBbox: { ...row.bestBbox },
|
|
@@ -58857,6 +59140,11 @@ var StationaryEvidenceLedger = class {
|
|
|
58857
59140
|
anchor,
|
|
58858
59141
|
agreed: numberOr(data.agreed, 0),
|
|
58859
59142
|
seen: numberOr(data.seen, 0),
|
|
59143
|
+
windowAgreed: numberOr(data.windowAgreed, 0),
|
|
59144
|
+
windowSeen: numberOr(data.windowSeen, 0),
|
|
59145
|
+
prevWindowAgreed: numberOr(data.prevWindowAgreed, 0),
|
|
59146
|
+
prevWindowSeen: numberOr(data.prevWindowSeen, 0),
|
|
59147
|
+
windowStartedAt: numberOr(data.windowStartedAt, updatedAt),
|
|
58860
59148
|
firstAgreedAt: numberOr(data.firstAgreedAt, updatedAt),
|
|
58861
59149
|
lastAgreedAt: numberOr(data.lastAgreedAt, updatedAt),
|
|
58862
59150
|
bestBbox,
|
|
@@ -67561,7 +67849,8 @@ var NC_SYSTEM_EVENT_CATEGORIES = [
|
|
|
67561
67849
|
require_dist.EventCategory.UpdateAvailable,
|
|
67562
67850
|
require_dist.EventCategory.AddonUpdated,
|
|
67563
67851
|
require_dist.EventCategory.SystemRestartCompleted,
|
|
67564
|
-
require_dist.EventCategory.RecordingExportCompleted
|
|
67852
|
+
require_dist.EventCategory.RecordingExportCompleted,
|
|
67853
|
+
require_dist.EventCategory.BackupRunCompleted
|
|
67565
67854
|
];
|
|
67566
67855
|
/**
|
|
67567
67856
|
* Install the addon's bus subscriptions. Identical set + relative order to the
|
|
@@ -70644,6 +70933,17 @@ function buildGlobalSettingsSchema(cap) {
|
|
|
70644
70933
|
default: DEVICE_ALERT_DEFAULTS.batteryLowPercent,
|
|
70645
70934
|
unit: "%"
|
|
70646
70935
|
},
|
|
70936
|
+
{
|
|
70937
|
+
type: "number",
|
|
70938
|
+
key: "consumableLowPercent",
|
|
70939
|
+
label: "Low consumable at",
|
|
70940
|
+
description: "At or below this, a measured consumable — a filter, a brush, a mop pad — is nearly exhausted. Its own number rather than the battery’s: a battery at 20% is hours from flat, a brush at 20% is weeks from worn. An item whose device reports “replace” outright is announced whatever this says.",
|
|
70941
|
+
min: 1,
|
|
70942
|
+
max: 99,
|
|
70943
|
+
step: 1,
|
|
70944
|
+
default: DEVICE_ALERT_DEFAULTS.consumableLowPercent,
|
|
70945
|
+
unit: "%"
|
|
70946
|
+
},
|
|
70647
70947
|
{
|
|
70648
70948
|
type: "number",
|
|
70649
70949
|
key: "alertRenotifyMinutes",
|
|
@@ -72155,6 +72455,92 @@ var RawGlobalStoreMirror = class {
|
|
|
72155
72455
|
}
|
|
72156
72456
|
};
|
|
72157
72457
|
//#endregion
|
|
72458
|
+
//#region src/pipeline-analytics/summary/group-crop.ts
|
|
72459
|
+
/** Padding around the union, as a fraction of its own size. */
|
|
72460
|
+
var GROUP_CROP_PADDING = .12;
|
|
72461
|
+
/**
|
|
72462
|
+
* Smallest crop, per axis, as a fraction of the frame.
|
|
72463
|
+
*
|
|
72464
|
+
* A quarter. Below that a thumbnail stops being legible at the sizes the
|
|
72465
|
+
* gallery draws it, and the extra context costs nothing — the subjects stay the
|
|
72466
|
+
* dominant thing in the picture at this size.
|
|
72467
|
+
*/
|
|
72468
|
+
var MIN_CROP_FRACTION = .25;
|
|
72469
|
+
/**
|
|
72470
|
+
* The crop, or `null` when there is nothing to cut to — no visible member, or
|
|
72471
|
+
* a frame with no dimensions. `null` means the caller keeps the full frame,
|
|
72472
|
+
* which is the only honest answer when nobody could say where the group was.
|
|
72473
|
+
*/
|
|
72474
|
+
function groupCropRect(input) {
|
|
72475
|
+
const { frameWidth: fw, frameHeight: fh } = input;
|
|
72476
|
+
if (fw <= 0 || fh <= 0) return null;
|
|
72477
|
+
const boxes = input.boxes.filter((b) => b.width > 0 && b.height > 0);
|
|
72478
|
+
if (boxes.length === 0) return null;
|
|
72479
|
+
let left = Number.POSITIVE_INFINITY;
|
|
72480
|
+
let top = Number.POSITIVE_INFINITY;
|
|
72481
|
+
let right = Number.NEGATIVE_INFINITY;
|
|
72482
|
+
let bottom = Number.NEGATIVE_INFINITY;
|
|
72483
|
+
for (const b of boxes) {
|
|
72484
|
+
left = Math.min(left, b.x);
|
|
72485
|
+
top = Math.min(top, b.y);
|
|
72486
|
+
right = Math.max(right, b.x + b.width);
|
|
72487
|
+
bottom = Math.max(bottom, b.y + b.height);
|
|
72488
|
+
}
|
|
72489
|
+
const padX = (right - left) * GROUP_CROP_PADDING;
|
|
72490
|
+
const padY = (bottom - top) * GROUP_CROP_PADDING;
|
|
72491
|
+
let x0 = left - padX;
|
|
72492
|
+
let y0 = top - padY;
|
|
72493
|
+
let x1 = right + padX;
|
|
72494
|
+
let y1 = bottom + padY;
|
|
72495
|
+
const minW = fw * MIN_CROP_FRACTION;
|
|
72496
|
+
const minH = fh * MIN_CROP_FRACTION;
|
|
72497
|
+
if (x1 - x0 < minW) {
|
|
72498
|
+
const cx = (x0 + x1) / 2;
|
|
72499
|
+
x0 = cx - minW / 2;
|
|
72500
|
+
x1 = cx + minW / 2;
|
|
72501
|
+
}
|
|
72502
|
+
if (y1 - y0 < minH) {
|
|
72503
|
+
const cy = (y0 + y1) / 2;
|
|
72504
|
+
y0 = cy - minH / 2;
|
|
72505
|
+
y1 = cy + minH / 2;
|
|
72506
|
+
}
|
|
72507
|
+
const width = Math.min(x1 - x0, fw);
|
|
72508
|
+
const height = Math.min(y1 - y0, fh);
|
|
72509
|
+
const x = Math.max(0, Math.min(x0, fw - width));
|
|
72510
|
+
const y = Math.max(0, Math.min(y0, fh - height));
|
|
72511
|
+
return {
|
|
72512
|
+
x: Math.round(x),
|
|
72513
|
+
y: Math.round(y),
|
|
72514
|
+
width: Math.round(width),
|
|
72515
|
+
height: Math.round(height)
|
|
72516
|
+
};
|
|
72517
|
+
}
|
|
72518
|
+
/**
|
|
72519
|
+
* Carry a crop computed on the DETECTION frame onto the NATIVE surface.
|
|
72520
|
+
*
|
|
72521
|
+
* The two are different sizes by construction: detection runs on a downscaled
|
|
72522
|
+
* frame and the group shot is fetched at up to 4096 px. Applying the rectangle
|
|
72523
|
+
* raw would cut a corner of the picture and call it the group — the failure
|
|
72524
|
+
* mode is silent and looks like a framing bug.
|
|
72525
|
+
*
|
|
72526
|
+
* Scaled per axis rather than by one factor, because the two surfaces are not
|
|
72527
|
+
* guaranteed to share an aspect, and clamped to the native frame so rounding
|
|
72528
|
+
* can never ask for a pixel that is not there.
|
|
72529
|
+
*/
|
|
72530
|
+
function scaleCropToFrame(crop, from, to) {
|
|
72531
|
+
if (from.width <= 0 || from.height <= 0 || to.width <= 0 || to.height <= 0) return null;
|
|
72532
|
+
const sx = to.width / from.width;
|
|
72533
|
+
const sy = to.height / from.height;
|
|
72534
|
+
const x = Math.max(0, Math.min(Math.round(crop.x * sx), to.width - 1));
|
|
72535
|
+
const y = Math.max(0, Math.min(Math.round(crop.y * sy), to.height - 1));
|
|
72536
|
+
return {
|
|
72537
|
+
x,
|
|
72538
|
+
y,
|
|
72539
|
+
width: Math.max(1, Math.min(Math.round(crop.width * sx), to.width - x)),
|
|
72540
|
+
height: Math.max(1, Math.min(Math.round(crop.height * sy), to.height - y))
|
|
72541
|
+
};
|
|
72542
|
+
}
|
|
72543
|
+
//#endregion
|
|
72158
72544
|
//#region src/pipeline-analytics/summary/summary-score.ts
|
|
72159
72545
|
/** A member whose own box is covered this much by others is hidden. */
|
|
72160
72546
|
var OCCLUDED_COVER_FRACTION = .5;
|
|
@@ -72203,12 +72589,24 @@ function scoreSummaryFrame(input) {
|
|
|
72203
72589
|
const visible = present.filter((d) => coveredFraction(d.bbox, present.filter((o) => o !== d).map((o) => o.bbox)) < OCCLUDED_COVER_FRACTION);
|
|
72204
72590
|
if (visible.length === 0) return {
|
|
72205
72591
|
visible: 0,
|
|
72206
|
-
quality: 0
|
|
72592
|
+
quality: 0,
|
|
72593
|
+
crop: null
|
|
72207
72594
|
};
|
|
72208
72595
|
const quality = visible.reduce((sum, d) => sum + memberQuality(d, input.frameWidth, input.frameHeight), 0) / visible.length;
|
|
72596
|
+
const crop = groupCropRect({
|
|
72597
|
+
boxes: visible.map((d) => ({
|
|
72598
|
+
x: d.bbox.x,
|
|
72599
|
+
y: d.bbox.y,
|
|
72600
|
+
width: d.bbox.width,
|
|
72601
|
+
height: d.bbox.height
|
|
72602
|
+
})),
|
|
72603
|
+
frameWidth: input.frameWidth,
|
|
72604
|
+
frameHeight: input.frameHeight
|
|
72605
|
+
});
|
|
72209
72606
|
return {
|
|
72210
72607
|
visible: visible.length,
|
|
72211
|
-
quality
|
|
72608
|
+
quality,
|
|
72609
|
+
crop
|
|
72212
72610
|
};
|
|
72213
72611
|
}
|
|
72214
72612
|
/**
|
|
@@ -72328,6 +72726,54 @@ var GroupShotTracker = class {
|
|
|
72328
72726
|
};
|
|
72329
72727
|
//#endregion
|
|
72330
72728
|
//#region src/pipeline-analytics/summary/group-shot-materialiser.ts
|
|
72729
|
+
/**
|
|
72730
|
+
* Apply the group crop to the native surface, or hand it back untouched.
|
|
72731
|
+
*
|
|
72732
|
+
* Every reason to skip is a reason NOT to guess: no rectangle, no detection
|
|
72733
|
+
* geometry to scale from, or a cropper this deployment does not have. The full
|
|
72734
|
+
* frame is worse than a crop and far better than a wrong crop.
|
|
72735
|
+
*/
|
|
72736
|
+
async function cropNative(native, input, deps) {
|
|
72737
|
+
const crop = input.crop;
|
|
72738
|
+
if (!crop || !deps.cropFrame) return native;
|
|
72739
|
+
const fw = input.cropFrameWidth;
|
|
72740
|
+
const fh = input.cropFrameHeight;
|
|
72741
|
+
if (fw === void 0 || fh === void 0) return native;
|
|
72742
|
+
const scaled = scaleCropToFrame(crop, {
|
|
72743
|
+
width: fw,
|
|
72744
|
+
height: fh
|
|
72745
|
+
}, native);
|
|
72746
|
+
if (scaled === null) return native;
|
|
72747
|
+
if (scaled.width >= native.width && scaled.height >= native.height) return native;
|
|
72748
|
+
try {
|
|
72749
|
+
const cut = await deps.cropFrame(native, scaled);
|
|
72750
|
+
if (cut === null) return native;
|
|
72751
|
+
const expected = cut.width * cut.height * 3;
|
|
72752
|
+
if (cut.format === "rgb" && cut.data.length !== expected) {
|
|
72753
|
+
deps.logger.warn("group shot crop returned a surface that is not its geometry", {
|
|
72754
|
+
tags: { deviceId: input.deviceId },
|
|
72755
|
+
meta: {
|
|
72756
|
+
summaryId: input.summaryId,
|
|
72757
|
+
width: cut.width,
|
|
72758
|
+
height: cut.height,
|
|
72759
|
+
expectedBytes: expected,
|
|
72760
|
+
gotBytes: cut.data.length
|
|
72761
|
+
}
|
|
72762
|
+
});
|
|
72763
|
+
return native;
|
|
72764
|
+
}
|
|
72765
|
+
return cut;
|
|
72766
|
+
} catch (err) {
|
|
72767
|
+
deps.logger.warn("group shot crop failed — storing the full frame", {
|
|
72768
|
+
tags: { deviceId: input.deviceId },
|
|
72769
|
+
meta: {
|
|
72770
|
+
summaryId: input.summaryId,
|
|
72771
|
+
error: require_dist.errMsg(err)
|
|
72772
|
+
}
|
|
72773
|
+
});
|
|
72774
|
+
return native;
|
|
72775
|
+
}
|
|
72776
|
+
}
|
|
72331
72777
|
async function persistSummaryGroupShot(deps, input) {
|
|
72332
72778
|
const { deviceId, summaryId, frameHandle } = input;
|
|
72333
72779
|
const drop = {
|
|
@@ -72361,7 +72807,8 @@ async function persistSummaryGroupShot(deps, input) {
|
|
|
72361
72807
|
});
|
|
72362
72808
|
return { kind: "ram-tier" };
|
|
72363
72809
|
}
|
|
72364
|
-
const
|
|
72810
|
+
const surface = await cropNative(native, input, deps);
|
|
72811
|
+
const { keyFrame, keyFrameSmall } = await deps.encodeVariants(surface);
|
|
72365
72812
|
const keyFrameMediaId = await deps.media.putReplacing({
|
|
72366
72813
|
deviceId,
|
|
72367
72814
|
ownerKind: "summary",
|
|
@@ -76237,6 +76684,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
76237
76684
|
},
|
|
76238
76685
|
readDeviceAlertSettings: () => ({
|
|
76239
76686
|
batteryLowPercent: this.deviceAlertSettingsMirror.batteryLowPercent,
|
|
76687
|
+
consumableLowPercent: this.deviceAlertSettingsMirror.consumableLowPercent,
|
|
76240
76688
|
renotifyMinutes: this.deviceAlertSettingsMirror.alertRenotifyMinutes,
|
|
76241
76689
|
notifyRecovery: this.deviceAlertSettingsMirror.alertNotifyRecovery
|
|
76242
76690
|
}),
|
|
@@ -79580,13 +80028,30 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
79580
80028
|
fetchNativeFullFrame: getNative,
|
|
79581
80029
|
encodeVariants: encodeKeyFrameVariants,
|
|
79582
80030
|
media: mediaStore,
|
|
79583
|
-
logger: this.ctx.logger
|
|
80031
|
+
logger: this.ctx.logger,
|
|
80032
|
+
cropFrame: async (frame, rect) => {
|
|
80033
|
+
const out = await extractCropRaw(frame.data, frame.width, frame.height, {
|
|
80034
|
+
x: rect.x / frame.width,
|
|
80035
|
+
y: rect.y / frame.height,
|
|
80036
|
+
w: rect.width / frame.width,
|
|
80037
|
+
h: rect.height / frame.height
|
|
80038
|
+
});
|
|
80039
|
+
return {
|
|
80040
|
+
...frame,
|
|
80041
|
+
data: out.data,
|
|
80042
|
+
width: out.width,
|
|
80043
|
+
height: out.height
|
|
80044
|
+
};
|
|
80045
|
+
}
|
|
79584
80046
|
}, {
|
|
79585
80047
|
deviceId,
|
|
79586
80048
|
summaryId,
|
|
79587
80049
|
timestamp: result.timestamp,
|
|
79588
80050
|
frameHandle,
|
|
79589
|
-
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH
|
|
80051
|
+
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH,
|
|
80052
|
+
crop: score.crop,
|
|
80053
|
+
cropFrameWidth: result.frameWidth,
|
|
80054
|
+
cropFrameHeight: result.frameHeight
|
|
79590
80055
|
})
|
|
79591
80056
|
}).then((outcome) => {
|
|
79592
80057
|
if (outcome?.kind === "landed") {
|