@camstack/addon-post-analysis 1.2.70 → 1.2.71
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-6T-F0YJO.mjs → dist-B--dE3Ry.mjs} +1 -1
- package/dist/{dist-DnpVHUcH.js → dist-DjPaVd2L.js} +1 -1
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Cxzt8LHT.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-3nHxWRIp.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-DW1qt-In.mjs → hostInit-_DSzSo5N.mjs} +1 -1
- package/dist/pipeline-analytics/index.js +290 -141
- package/dist/pipeline-analytics/index.mjs +290 -141
- 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-DjPaVd2L.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");
|
|
@@ -555,7 +555,14 @@ function alarmCoverageGap(coverage) {
|
|
|
555
555
|
return "all-skipped";
|
|
556
556
|
}
|
|
557
557
|
/**
|
|
558
|
-
* "
|
|
558
|
+
* "Attivi: Cancello, Garage — Esclusi: Porta retro."
|
|
559
|
+
*
|
|
560
|
+
* A LIST, not a sentence. The mode is in the title and the body is one
|
|
561
|
+
* `Label: names` line per device group, because this is read on a lock screen
|
|
562
|
+
* and the question is always "which one" — the prose version ("3 dispositivi
|
|
563
|
+
* attivi: …. Esclusi: ….") made the operator parse a paragraph to find a
|
|
564
|
+
* window. Each variant still owns its own catalog key, so the Texts tab can
|
|
565
|
+
* re-word "nothing is armed" without touching "these are armed".
|
|
559
566
|
*
|
|
560
567
|
* Names, not ids: this is read by a person standing at a door. An id that has
|
|
561
568
|
* no name falls back to a NAMED, translated placeholder rather than being
|
|
@@ -634,7 +641,7 @@ function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
|
634
641
|
};
|
|
635
642
|
}
|
|
636
643
|
/**
|
|
637
|
-
* "
|
|
644
|
+
* "Attivi: 2 — Bypassati: Finestra veranda."
|
|
638
645
|
*
|
|
639
646
|
* The bypassed sensors are named and the armed ones are COUNTED, not listed:
|
|
640
647
|
* this sentence's subject is the exception, and burying it under thirteen
|
|
@@ -657,13 +664,13 @@ function armedWithBypass(coverage, bypassed, deviceName) {
|
|
|
657
664
|
};
|
|
658
665
|
}
|
|
659
666
|
/**
|
|
660
|
-
* "
|
|
667
|
+
* "Aperti: Finestra veranda, Porta retro" + how to arm anyway.
|
|
661
668
|
*
|
|
662
669
|
* The one alarm sentence about something that did not happen, so it has to
|
|
663
670
|
* carry both halves an operator needs to act: WHICH sensors are open, and that
|
|
664
671
|
* pressing arm again arms without them. Naming them is the whole message — a
|
|
665
672
|
* refusal that says only "cannot arm" is a panel the operator will conclude is
|
|
666
|
-
* broken.
|
|
673
|
+
* broken — so the names are the FIRST line and the retry hint is the second.
|
|
667
674
|
*/
|
|
668
675
|
function buildArmRefusedAnnouncement(mode, names, retrySeconds) {
|
|
669
676
|
return {
|
|
@@ -678,7 +685,7 @@ function buildArmRefusedAnnouncement(mode, names, retrySeconds) {
|
|
|
678
685
|
};
|
|
679
686
|
}
|
|
680
687
|
/**
|
|
681
|
-
* The same coverage, said 30 seconds earlier
|
|
688
|
+
* The EXCEPTIONS to the same coverage, said 30 seconds earlier.
|
|
682
689
|
*
|
|
683
690
|
* The exit-delay sentence is deliberately SHORTER than the arm one — it is read
|
|
684
691
|
* on the way out of a door — and it carries exactly one thing the arm sentence
|
|
@@ -686,8 +693,15 @@ function buildArmRefusedAnnouncement(mode, names, retrySeconds) {
|
|
|
686
693
|
* A mode that arms nothing is worth interrupting somebody for at second 0 and
|
|
687
694
|
* merely worth recording at second 30.
|
|
688
695
|
*
|
|
689
|
-
*
|
|
690
|
-
*
|
|
696
|
+
* **It never repeats the device list.** The countdown lives in the TITLE
|
|
697
|
+
* (`system.alarm-arming.title.<mode>` renders `{{seconds}}`), and the armed
|
|
698
|
+
* list arrives thirty seconds later in the arm announcement — printing it
|
|
699
|
+
* twice is the verbosity that made the operator stop reading both. So the body
|
|
700
|
+
* is what is WRONG: what was bypassed, what is excluded, or that nothing is.
|
|
701
|
+
*
|
|
702
|
+
* `seconds` stays in the params because the title asks for it, and `textCount`
|
|
703
|
+
* stays with it: a row enqueued before this deploy still pluralises on the
|
|
704
|
+
* countdown it was written for.
|
|
691
705
|
*/
|
|
692
706
|
function buildArmingAnnouncement(coverage, seconds, deviceName, bypassed = []) {
|
|
693
707
|
const textParams = {
|
|
@@ -702,6 +716,7 @@ function buildArmingAnnouncement(coverage, seconds, deviceName, bypassed = []) {
|
|
|
702
716
|
},
|
|
703
717
|
textCount: seconds
|
|
704
718
|
};
|
|
719
|
+
const skippedNames = coverage.skippedDevices.map((s) => deviceName(s.deviceId)).join(", ");
|
|
705
720
|
const gap = alarmCoverageGap(coverage);
|
|
706
721
|
if (gap === "uncovered") return {
|
|
707
722
|
bodyKey: "alarm.arming.body.uncovered",
|
|
@@ -712,10 +727,18 @@ function buildArmingAnnouncement(coverage, seconds, deviceName, bypassed = []) {
|
|
|
712
727
|
bodyKey: "alarm.arming.body.allSkipped",
|
|
713
728
|
textParams: {
|
|
714
729
|
...textParams,
|
|
715
|
-
names:
|
|
730
|
+
names: skippedNames
|
|
716
731
|
},
|
|
717
732
|
textCount: seconds
|
|
718
733
|
};
|
|
734
|
+
if (coverage.skippedDevices.length > 0) return {
|
|
735
|
+
bodyKey: "alarm.arming.body.skipped",
|
|
736
|
+
textParams: {
|
|
737
|
+
...textParams,
|
|
738
|
+
names: skippedNames
|
|
739
|
+
},
|
|
740
|
+
textCount: coverage.skippedDevices.length
|
|
741
|
+
};
|
|
719
742
|
return {
|
|
720
743
|
bodyKey: "alarm.arming.body",
|
|
721
744
|
textParams,
|
|
@@ -4342,69 +4365,38 @@ var en_default = {
|
|
|
4342
4365
|
"snooze.scope.anyDevice": "A camera",
|
|
4343
4366
|
"snooze.scope.anyRule": "A rule",
|
|
4344
4367
|
"snooze.scope.rule": "“{{rule}}”",
|
|
4345
|
-
"alarm.arm.body.allDevices":
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
},
|
|
4349
|
-
"alarm.arm.body.
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
"alarm.
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
},
|
|
4357
|
-
"alarm.
|
|
4358
|
-
"one": "{{devices}} device armed: {{names}}. Excluded: {{skippedNames}}.",
|
|
4359
|
-
"other": "{{devices}} devices armed: {{names}}. Excluded: {{skippedNames}}."
|
|
4360
|
-
},
|
|
4361
|
-
"alarm.arm.body.uncovered": "Nothing is armed — no enabled rule is gated on this mode.",
|
|
4362
|
-
"alarm.arm.body.allSkipped": {
|
|
4363
|
-
"one": "Nothing is armed: the {{devices}} device this mode covers is excluded ({{names}}).",
|
|
4364
|
-
"other": "Nothing is armed: all {{devices}} devices this mode covers are excluded ({{names}})."
|
|
4365
|
-
},
|
|
4366
|
-
"alarm.arming.body": {
|
|
4367
|
-
"one": "Armed in {{seconds}} second.",
|
|
4368
|
-
"other": "Armed in {{seconds}} seconds."
|
|
4369
|
-
},
|
|
4370
|
-
"alarm.arming.body.uncovered": {
|
|
4371
|
-
"one": "Armed in {{seconds}} second — but no enabled rule is gated on this mode, so it will protect nothing.",
|
|
4372
|
-
"other": "Armed in {{seconds}} seconds — but no enabled rule is gated on this mode, so it will protect nothing."
|
|
4373
|
-
},
|
|
4374
|
-
"alarm.arming.body.allSkipped": {
|
|
4375
|
-
"one": "Armed in {{seconds}} second — but every device this mode covers is excluded ({{names}}), so it will protect nothing.",
|
|
4376
|
-
"other": "Armed in {{seconds}} seconds — but every device this mode covers is excluded ({{names}}), so it will protect nothing."
|
|
4377
|
-
},
|
|
4378
|
-
"alarm.arm.body.bypassed": {
|
|
4379
|
-
"one": "{{devices}} device armed — open and excluded: {{bypassedNames}}.",
|
|
4380
|
-
"other": "{{devices}} devices armed — open and excluded: {{bypassedNames}}."
|
|
4381
|
-
},
|
|
4382
|
-
"alarm.arming.body.bypassed": {
|
|
4383
|
-
"one": "Armed in {{seconds}} second, excluding what is open: {{bypassedNames}}.",
|
|
4384
|
-
"other": "Armed in {{seconds}} seconds, excluding what is open: {{bypassedNames}}."
|
|
4385
|
-
},
|
|
4386
|
-
"alarm.armRefused.body": {
|
|
4387
|
-
"one": "{{names}} is open. Press arm again within {{seconds}}s to arm without it.",
|
|
4388
|
-
"other": "These are open: {{names}}. Press arm again within {{seconds}}s to arm without them."
|
|
4389
|
-
},
|
|
4368
|
+
"alarm.arm.body.allDevices": "Active: all devices",
|
|
4369
|
+
"alarm.arm.body.noDevices": "Active: no device restriction",
|
|
4370
|
+
"alarm.arm.body.devices": "Active: {{names}}",
|
|
4371
|
+
"alarm.arm.body.devicesSkipped": "Active: {{names}}\nExcluded: {{skippedNames}}",
|
|
4372
|
+
"alarm.arm.body.uncovered": "Active: none — no rule uses this mode",
|
|
4373
|
+
"alarm.arm.body.allSkipped": "Active: none\nExcluded: {{names}}",
|
|
4374
|
+
"alarm.arming.body": "No exclusions",
|
|
4375
|
+
"alarm.arming.body.uncovered": "Active: none — no rule uses this mode",
|
|
4376
|
+
"alarm.arming.body.allSkipped": "Active: none\nExcluded: {{names}}",
|
|
4377
|
+
"alarm.arming.body.skipped": "Excluded: {{names}}",
|
|
4378
|
+
"alarm.arm.body.bypassed": "Active: {{devices}}\nBypassed: {{bypassedNames}}",
|
|
4379
|
+
"alarm.arming.body.bypassed": "Bypassed: {{bypassedNames}}",
|
|
4380
|
+
"alarm.armRefused.body": "Open: {{names}}\nRepeat within {{seconds}}s to arm with bypass.",
|
|
4390
4381
|
"alarm.device.unnamed": "Device {{id}}",
|
|
4391
|
-
"
|
|
4392
|
-
"system.alarm-arm-refused.title.
|
|
4393
|
-
"system.alarm-arm-refused.title.
|
|
4394
|
-
"system.alarm-arm-refused.
|
|
4395
|
-
"system.alarm-
|
|
4396
|
-
"system.alarm-arming.title.
|
|
4397
|
-
"system.alarm-arming.title.
|
|
4398
|
-
"system.alarm-arming.
|
|
4399
|
-
"system.alarm-
|
|
4400
|
-
"system.alarm-armed.title.
|
|
4401
|
-
"system.alarm-armed.title.
|
|
4402
|
-
"system.alarm-armed.
|
|
4403
|
-
"system.alarm-
|
|
4404
|
-
"system.alarm-disarmed.
|
|
4382
|
+
"alarm.combined.title": "Alarm: {{deviceName}}",
|
|
4383
|
+
"system.alarm-arm-refused.title.home": "Arming home refused",
|
|
4384
|
+
"system.alarm-arm-refused.title.away": "Arming away refused",
|
|
4385
|
+
"system.alarm-arm-refused.title.night": "Arming night refused",
|
|
4386
|
+
"system.alarm-arm-refused.body": "Arming refused",
|
|
4387
|
+
"system.alarm-arming.title.home": "Arming home — {{seconds}}s",
|
|
4388
|
+
"system.alarm-arming.title.away": "Arming away — {{seconds}}s",
|
|
4389
|
+
"system.alarm-arming.title.night": "Arming night — {{seconds}}s",
|
|
4390
|
+
"system.alarm-arming.body": "Arming",
|
|
4391
|
+
"system.alarm-armed.title.home": "Armed: home",
|
|
4392
|
+
"system.alarm-armed.title.away": "Armed: away",
|
|
4393
|
+
"system.alarm-armed.title.night": "Armed: night",
|
|
4394
|
+
"system.alarm-armed.body": "Alarm armed",
|
|
4395
|
+
"system.alarm-disarmed.title": "Disarmed",
|
|
4396
|
+
"system.alarm-disarmed.body": "Alarm disarmed",
|
|
4405
4397
|
"system.alarm-triggered.title": "ALARM",
|
|
4406
|
-
"system.alarm-triggered.body": "
|
|
4407
|
-
"system.alarm-triggered.body.rule": "
|
|
4398
|
+
"system.alarm-triggered.body": "Alarm triggered",
|
|
4399
|
+
"system.alarm-triggered.body.rule": "Triggered by {{rule}} on {{deviceName}}",
|
|
4408
4400
|
"button.snooze.minutes": "Snooze {{minutes}} min",
|
|
4409
4401
|
"button.snooze.hours": {
|
|
4410
4402
|
"one": "Snooze {{hours}} hour",
|
|
@@ -4536,69 +4528,38 @@ var it_default = {
|
|
|
4536
4528
|
"snooze.scope.anyDevice": "Una camera",
|
|
4537
4529
|
"snooze.scope.anyRule": "Una regola",
|
|
4538
4530
|
"snooze.scope.rule": "“{{rule}}”",
|
|
4539
|
-
"alarm.arm.body.allDevices":
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
},
|
|
4543
|
-
"alarm.arm.body.
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
"alarm.
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
},
|
|
4551
|
-
"alarm.
|
|
4552
|
-
"one": "{{devices}} dispositivo attivo: {{names}}. Esclusi: {{skippedNames}}.",
|
|
4553
|
-
"other": "{{devices}} dispositivi attivi: {{names}}. Esclusi: {{skippedNames}}."
|
|
4554
|
-
},
|
|
4555
|
-
"alarm.arm.body.uncovered": "Non è attivo nulla — nessuna regola abilitata è legata a questa modalità.",
|
|
4556
|
-
"alarm.arm.body.allSkipped": {
|
|
4557
|
-
"one": "Non è attivo nulla: l'unico dispositivo ({{devices}}) coperto da questa modalità è escluso ({{names}}).",
|
|
4558
|
-
"other": "Non è attivo nulla: tutti i {{devices}} dispositivi coperti da questa modalità sono esclusi ({{names}})."
|
|
4559
|
-
},
|
|
4560
|
-
"alarm.arming.body": {
|
|
4561
|
-
"one": "Attivo tra {{seconds}} secondo.",
|
|
4562
|
-
"other": "Attivo tra {{seconds}} secondi."
|
|
4563
|
-
},
|
|
4564
|
-
"alarm.arming.body.uncovered": {
|
|
4565
|
-
"one": "Attivo tra {{seconds}} secondo — ma nessuna regola abilitata è legata a questa modalità: non proteggerà nulla.",
|
|
4566
|
-
"other": "Attivo tra {{seconds}} secondi — ma nessuna regola abilitata è legata a questa modalità: non proteggerà nulla."
|
|
4567
|
-
},
|
|
4568
|
-
"alarm.arming.body.allSkipped": {
|
|
4569
|
-
"one": "Attivo tra {{seconds}} secondo — ma ogni dispositivo coperto da questa modalità è escluso ({{names}}): non proteggerà nulla.",
|
|
4570
|
-
"other": "Attivo tra {{seconds}} secondi — ma ogni dispositivo coperto da questa modalità è escluso ({{names}}): non proteggerà nulla."
|
|
4571
|
-
},
|
|
4572
|
-
"alarm.arm.body.bypassed": {
|
|
4573
|
-
"one": "{{devices}} dispositivo attivo — aperto ed escluso: {{bypassedNames}}.",
|
|
4574
|
-
"other": "{{devices}} dispositivi attivi — aperti ed esclusi: {{bypassedNames}}."
|
|
4575
|
-
},
|
|
4576
|
-
"alarm.arming.body.bypassed": {
|
|
4577
|
-
"one": "Attivo tra {{seconds}} secondo, escludendo ciò che è aperto: {{bypassedNames}}.",
|
|
4578
|
-
"other": "Attivo tra {{seconds}} secondi, escludendo ciò che è aperto: {{bypassedNames}}."
|
|
4579
|
-
},
|
|
4580
|
-
"alarm.armRefused.body": {
|
|
4581
|
-
"one": "{{names}} è aperto. Premi di nuovo entro {{seconds}}s per inserire senza di esso.",
|
|
4582
|
-
"other": "Sono aperti: {{names}}. Premi di nuovo entro {{seconds}}s per inserire senza di essi."
|
|
4583
|
-
},
|
|
4531
|
+
"alarm.arm.body.allDevices": "Attivi: tutti i dispositivi",
|
|
4532
|
+
"alarm.arm.body.noDevices": "Attivi: nessuna restrizione",
|
|
4533
|
+
"alarm.arm.body.devices": "Attivi: {{names}}",
|
|
4534
|
+
"alarm.arm.body.devicesSkipped": "Attivi: {{names}}\nEsclusi: {{skippedNames}}",
|
|
4535
|
+
"alarm.arm.body.uncovered": "Attivi: nessuno — nessuna regola usa questa modalità",
|
|
4536
|
+
"alarm.arm.body.allSkipped": "Attivi: nessuno\nEsclusi: {{names}}",
|
|
4537
|
+
"alarm.arming.body": "Nessuna esclusione",
|
|
4538
|
+
"alarm.arming.body.uncovered": "Attivi: nessuno — nessuna regola usa questa modalità",
|
|
4539
|
+
"alarm.arming.body.allSkipped": "Attivi: nessuno\nEsclusi: {{names}}",
|
|
4540
|
+
"alarm.arming.body.skipped": "Esclusi: {{names}}",
|
|
4541
|
+
"alarm.arm.body.bypassed": "Attivi: {{devices}}\nBypassati: {{bypassedNames}}",
|
|
4542
|
+
"alarm.arming.body.bypassed": "Bypassati: {{bypassedNames}}",
|
|
4543
|
+
"alarm.armRefused.body": "Aperti: {{names}}\nRipeti entro {{seconds}}s per inserire con bypass.",
|
|
4584
4544
|
"alarm.device.unnamed": "Dispositivo {{id}}",
|
|
4545
|
+
"alarm.combined.title": "Allarme: {{deviceName}}",
|
|
4585
4546
|
"system.alarm-arm-refused.title.home": "Inserimento Casa rifiutato",
|
|
4586
4547
|
"system.alarm-arm-refused.title.away": "Inserimento Fuori rifiutato",
|
|
4587
4548
|
"system.alarm-arm-refused.title.night": "Inserimento Notte rifiutato",
|
|
4588
|
-
"system.alarm-arm-refused.body": "
|
|
4589
|
-
"system.alarm-arming.title.home": "Inserimento Casa",
|
|
4590
|
-
"system.alarm-arming.title.away": "Inserimento Fuori",
|
|
4591
|
-
"system.alarm-arming.title.night": "Inserimento Notte",
|
|
4592
|
-
"system.alarm-arming.body": "
|
|
4593
|
-
"system.alarm-armed.title.home": "Casa
|
|
4594
|
-
"system.alarm-armed.title.away": "Fuori
|
|
4595
|
-
"system.alarm-armed.title.night": "Notte
|
|
4596
|
-
"system.alarm-armed.body": "
|
|
4597
|
-
"system.alarm-disarmed.title": "
|
|
4598
|
-
"system.alarm-disarmed.body": "
|
|
4549
|
+
"system.alarm-arm-refused.body": "Inserimento rifiutato",
|
|
4550
|
+
"system.alarm-arming.title.home": "Inserimento Casa — {{seconds}}s",
|
|
4551
|
+
"system.alarm-arming.title.away": "Inserimento Fuori — {{seconds}}s",
|
|
4552
|
+
"system.alarm-arming.title.night": "Inserimento Notte — {{seconds}}s",
|
|
4553
|
+
"system.alarm-arming.body": "Inserimento in corso",
|
|
4554
|
+
"system.alarm-armed.title.home": "Inserito: Casa",
|
|
4555
|
+
"system.alarm-armed.title.away": "Inserito: Fuori",
|
|
4556
|
+
"system.alarm-armed.title.night": "Inserito: Notte",
|
|
4557
|
+
"system.alarm-armed.body": "Allarme inserito",
|
|
4558
|
+
"system.alarm-disarmed.title": "Disinserito",
|
|
4559
|
+
"system.alarm-disarmed.body": "Allarme disinserito",
|
|
4599
4560
|
"system.alarm-triggered.title": "ALLARME",
|
|
4600
|
-
"system.alarm-triggered.body": "
|
|
4601
|
-
"system.alarm-triggered.body.rule": "
|
|
4561
|
+
"system.alarm-triggered.body": "Allarme scattato",
|
|
4562
|
+
"system.alarm-triggered.body.rule": "Scattato da {{rule}} su {{deviceName}}",
|
|
4602
4563
|
"button.snooze.minutes": "Silenzia {{minutes}} min",
|
|
4603
4564
|
"button.snooze.hours": {
|
|
4604
4565
|
"one": "Silenzia {{hours}} ora",
|
|
@@ -5317,7 +5278,8 @@ var NcDispatcher = class {
|
|
|
5317
5278
|
const systemEvent = subject.systemEvent;
|
|
5318
5279
|
const displayDeviceId = systemEvent?.deviceId ?? subject.deviceId;
|
|
5319
5280
|
const deviceName = systemEvent !== void 0 && systemEvent.deviceId === void 0 ? "system" : await this.deps.getDeviceName(displayDeviceId).catch(() => null) ?? `camera ${displayDeviceId}`;
|
|
5320
|
-
const
|
|
5281
|
+
const alarmSource = systemEvent?.alarmSource;
|
|
5282
|
+
const zoneLabels = systemEvent === void 0 ? await resolveZoneLabels(this.deps.getZoneNames, subject.deviceId, subject.zones) : alarmSource?.zones !== void 0 && alarmSource.zones.length > 0 ? await resolveZoneLabels(this.deps.getZoneNames, alarmSource.deviceId, alarmSource.zones) : [];
|
|
5321
5283
|
const vars = buildTemplateVars(this.texts, entry, deviceName, zoneLabels);
|
|
5322
5284
|
const systemCount = systemEvent?.textCount !== void 0 ? { count: systemEvent.textCount } : {};
|
|
5323
5285
|
const title = this.renderText({
|
|
@@ -5660,6 +5622,10 @@ async function resolveZoneLabels(getZoneNames, deviceId, zoneIds) {
|
|
|
5660
5622
|
return [...zoneIds];
|
|
5661
5623
|
}
|
|
5662
5624
|
}
|
|
5625
|
+
/** `{{confidence}}` as a whole percent, or empty when nothing measured one. */
|
|
5626
|
+
function percentOf(confidence) {
|
|
5627
|
+
return confidence !== void 0 ? `${Math.round(confidence * 100)}%` : "";
|
|
5628
|
+
}
|
|
5663
5629
|
/**
|
|
5664
5630
|
* The variable map ONE render pass sees: scalars, then the composites built
|
|
5665
5631
|
* from them, then the params a system-event row froze at intake.
|
|
@@ -5675,19 +5641,23 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels) {
|
|
|
5675
5641
|
const subject = entry.payload.subject;
|
|
5676
5642
|
const occupancy = subject.occupancy;
|
|
5677
5643
|
const systemEvent = subject.systemEvent;
|
|
5644
|
+
const detection = systemEvent?.alarmSource?.className !== void 0 ? systemEvent.alarmSource : void 0;
|
|
5678
5645
|
return {
|
|
5679
5646
|
camera: deviceName,
|
|
5680
|
-
class: subject.className,
|
|
5681
|
-
label: subject.label ?? "",
|
|
5647
|
+
class: detection?.className ?? subject.className,
|
|
5648
|
+
label: detection?.label ?? subject.label ?? "",
|
|
5682
5649
|
zones: zoneLabels.join(", "),
|
|
5683
|
-
subject:
|
|
5650
|
+
subject: detection?.className !== void 0 ? ncSubjectText(texts, {
|
|
5651
|
+
className: detection.className,
|
|
5652
|
+
...detection.label !== void 0 ? { label: detection.label } : {}
|
|
5653
|
+
}) : systemEvent?.subject ?? ncSubjectText(texts, {
|
|
5684
5654
|
className: subject.className,
|
|
5685
5655
|
...subject.label !== void 0 ? { label: subject.label } : {}
|
|
5686
5656
|
}),
|
|
5687
5657
|
inZones: ncInZonesText(texts, zoneLabels),
|
|
5688
5658
|
scope: occupancy?.zone ?? deviceName,
|
|
5689
5659
|
zone: occupancy?.zone ?? zoneLabels[0] ?? "",
|
|
5690
|
-
confidence:
|
|
5660
|
+
confidence: percentOf(detection?.confidence ?? subject.confidence),
|
|
5691
5661
|
time: new Date(subject.timestamp).toLocaleTimeString(),
|
|
5692
5662
|
rule: entry.payload.ruleName,
|
|
5693
5663
|
count: occupancy !== void 0 ? `${occupancy.count}` : "",
|
|
@@ -6227,6 +6197,35 @@ function incomingFromSystemEvent(event, lookup) {
|
|
|
6227
6197
|
}
|
|
6228
6198
|
}
|
|
6229
6199
|
/**
|
|
6200
|
+
* The COMBINED row's two keys, or null when this transition is not one.
|
|
6201
|
+
*
|
|
6202
|
+
* A rule that trips the alarm produces ONE notification, and the operator has
|
|
6203
|
+
* to be able to read it: the title is the severity marker over the camera
|
|
6204
|
+
* (`alarm.combined.title`, shipped as `Alarm: {{deviceName}}` and re-wordable
|
|
6205
|
+
* per rule or in the Texts tab), and the body is `detection.body` — the exact
|
|
6206
|
+
* sentence that rule's ordinary notification would have carried, with its own
|
|
6207
|
+
* class, identity and zones.
|
|
6208
|
+
*
|
|
6209
|
+
* The alarm used to write its own prose here ("The alarm was triggered by
|
|
6210
|
+
* Person at gate on Ingresso"), which made the one notification that matters
|
|
6211
|
+
* the only one in the system that did not look like the rule's others: no
|
|
6212
|
+
* identity, no zone, a different subject. The prefix carries the severity; the
|
|
6213
|
+
* familiar line carries the meaning.
|
|
6214
|
+
*
|
|
6215
|
+
* **Gated on the detection vocabulary, not on the source.** `detection.body`
|
|
6216
|
+
* renders `{{subject}}`, and a fire ref with no class composes it from the
|
|
6217
|
+
* system event's dedup slot — "alarm:42 su Ingresso". A source without a class
|
|
6218
|
+
* therefore keeps the alarm's own sentence, which is also what every row
|
|
6219
|
+
* enqueued before this deploy carries.
|
|
6220
|
+
*/
|
|
6221
|
+
function combinedAlarmText(source) {
|
|
6222
|
+
if (source?.className === void 0) return null;
|
|
6223
|
+
return {
|
|
6224
|
+
titleKey: "alarm.combined.title",
|
|
6225
|
+
bodyKey: "detection.body"
|
|
6226
|
+
};
|
|
6227
|
+
}
|
|
6228
|
+
/**
|
|
6230
6229
|
* The panel's own transition, as a system-event subject.
|
|
6231
6230
|
*
|
|
6232
6231
|
* IN-PROCESS, not off the bus. The alarm and the Notification Center live in
|
|
@@ -6249,6 +6248,7 @@ function incomingFromAlarmTransition(input) {
|
|
|
6249
6248
|
const deviceId = source?.deviceId ?? panelDeviceId;
|
|
6250
6249
|
const identity = input.lookup(deviceId);
|
|
6251
6250
|
const kind = transition.kind;
|
|
6251
|
+
const combined = combinedAlarmText(source);
|
|
6252
6252
|
return systemSubject({
|
|
6253
6253
|
facts: {
|
|
6254
6254
|
eventId: `alarm:${kind}:${String(transition.at)}`,
|
|
@@ -6264,12 +6264,13 @@ function incomingFromAlarmTransition(input) {
|
|
|
6264
6264
|
},
|
|
6265
6265
|
kind,
|
|
6266
6266
|
subject: `alarm:${String(panelDeviceId)}`,
|
|
6267
|
-
titleKey: transition.mode !== void 0 ? `system.${kind}.title.${transition.mode}` : `system.${kind}.title`,
|
|
6268
|
-
bodyKey: input.body?.bodyKey ?? (source !== void 0 ? `system.${kind}.body.rule` : `system.${kind}.body`),
|
|
6267
|
+
titleKey: combined !== null ? combined.titleKey : transition.mode !== void 0 ? `system.${kind}.title.${transition.mode}` : `system.${kind}.title`,
|
|
6268
|
+
bodyKey: combined?.bodyKey ?? input.body?.bodyKey ?? (source !== void 0 ? `system.${kind}.body.rule` : `system.${kind}.body`),
|
|
6269
6269
|
textParams: {
|
|
6270
6270
|
...deviceTextParams(deviceId, identity),
|
|
6271
6271
|
...transition.mode !== void 0 ? { mode: transition.mode } : {},
|
|
6272
6272
|
...source !== void 0 ? { rule: source.ruleName } : {},
|
|
6273
|
+
...kind === "alarm-arming" ? { seconds: `${transition.seconds ?? 0}` } : {},
|
|
6273
6274
|
...input.body?.textParams
|
|
6274
6275
|
},
|
|
6275
6276
|
deviceId,
|
|
@@ -12584,6 +12585,7 @@ function frozenOccupancy(rule, occ) {
|
|
|
12584
12585
|
*/
|
|
12585
12586
|
function fireRefFor(rule, subject, kind) {
|
|
12586
12587
|
const hasEventMedia = kind === "object-event" || kind === "package-event";
|
|
12588
|
+
const className = subject.classNames[0];
|
|
12587
12589
|
return {
|
|
12588
12590
|
ruleId: rule.id,
|
|
12589
12591
|
ruleName: rule.name,
|
|
@@ -12592,7 +12594,11 @@ function fireRefFor(rule, subject, kind) {
|
|
|
12592
12594
|
recordId: subject.recordId,
|
|
12593
12595
|
...subject.trackId !== void 0 ? { trackId: subject.trackId } : {},
|
|
12594
12596
|
...hasEventMedia ? { eventId: subject.recordId } : {},
|
|
12595
|
-
timestamp: subject.timestamp
|
|
12597
|
+
timestamp: subject.timestamp,
|
|
12598
|
+
...className !== void 0 ? { className } : {},
|
|
12599
|
+
...subject.label !== void 0 ? { label: subject.label } : {},
|
|
12600
|
+
zones: notificationZones(rule, subject),
|
|
12601
|
+
...subject.confidence !== void 0 ? { confidence: subject.confidence } : {}
|
|
12596
12602
|
};
|
|
12597
12603
|
}
|
|
12598
12604
|
function notificationZones(rule, subject) {
|
|
@@ -36070,6 +36076,138 @@ var BindingCache = class {
|
|
|
36070
36076
|
}
|
|
36071
36077
|
};
|
|
36072
36078
|
//#endregion
|
|
36079
|
+
//#region src/pipeline-analytics/runtime/detection-coverage-cache.ts
|
|
36080
|
+
/**
|
|
36081
|
+
* DetectionCoverageCache — fleet-seeded, event-invalidated mirror of WHICH
|
|
36082
|
+
* devices carry a WRAPPED `detection-pipeline` binding. This is the alarm's
|
|
36083
|
+
* coverage authority ("Away armed, 12 cameras" is false if three of them are
|
|
36084
|
+
* offline or no longer wrapped), read by the notification centre's device
|
|
36085
|
+
* directory (`listDeviceIdentities`).
|
|
36086
|
+
*
|
|
36087
|
+
* WHY a mirror, not a per-tick sweep. The directory refreshes on the NC's 30 s
|
|
36088
|
+
* rule-reload tick. Answering "who still has detection?" with a fleet-wide
|
|
36089
|
+
* `getAllBindings` on EVERY tick cost 903 ms / 373 KB / ~505 `getBindings:
|
|
36090
|
+
* default wrappers skipped` log lines on the live hub (524 devices), forever —
|
|
36091
|
+
* ~3% of hub-main's main thread plus a permanent log-spam baseline. Same defect
|
|
36092
|
+
* and fix as `SystemMirror.refreshBinding` (639073e8): ONE fleet read to warm,
|
|
36093
|
+
* then per-device updates driven by `DeviceBindingsChanged` events. A wrapper
|
|
36094
|
+
* toggle emits that event carrying the device's id and `capName`
|
|
36095
|
+
* (`detection-pipeline`), so a single `getBindings(deviceId)` — 21 ms / 613 B —
|
|
36096
|
+
* keeps the mirror current without ever touching the fleet again.
|
|
36097
|
+
*
|
|
36098
|
+
* ── Destructive-direction discipline (D49) ──────────────────────────────────
|
|
36099
|
+
*
|
|
36100
|
+
* `detectionActive: false` reports a camera as UNCOVERED / switched-off (D62) —
|
|
36101
|
+
* the destructive answer, and the one an operator reads as "my alarm is not
|
|
36102
|
+
* watching that door". A transient `getBindings` blip must never flip a
|
|
36103
|
+
* known-covered camera to uncovered:
|
|
36104
|
+
* • a failed refresh KEEPS the previous value (a read that throws changes
|
|
36105
|
+
* nothing), and
|
|
36106
|
+
* • a single read that would flip a known `true → false` is confirmed by a
|
|
36107
|
+
* concordant SECOND read before it lands — an empty binding list is not
|
|
36108
|
+
* proof the wrapper is gone.
|
|
36109
|
+
* A COLD mirror (seed not yet landed, or a seed that failed) answers `undefined`
|
|
36110
|
+
* for every id — and unknown never reports a camera as excluded from coverage.
|
|
36111
|
+
*/
|
|
36112
|
+
var DetectionCoverageCache = class {
|
|
36113
|
+
api;
|
|
36114
|
+
logger;
|
|
36115
|
+
/** deviceId → "has a WRAPPED detection-pipeline binding". Absent = unknown. */
|
|
36116
|
+
state = /* @__PURE__ */ new Map();
|
|
36117
|
+
warm = false;
|
|
36118
|
+
seeding = null;
|
|
36119
|
+
constructor(deps) {
|
|
36120
|
+
this.api = deps.api;
|
|
36121
|
+
this.logger = deps.logger;
|
|
36122
|
+
}
|
|
36123
|
+
/** Has the ONE fleet seed ever landed? A cold mirror knows nothing. */
|
|
36124
|
+
isWarm() {
|
|
36125
|
+
return this.warm;
|
|
36126
|
+
}
|
|
36127
|
+
/** Do we have an answer for this id at all? (`false` ⇒ absent ⇒ unknown.) */
|
|
36128
|
+
has(deviceId) {
|
|
36129
|
+
return this.state.has(deviceId);
|
|
36130
|
+
}
|
|
36131
|
+
/** The coverage answer, or `undefined` when this id is unknown. No I/O. */
|
|
36132
|
+
get(deviceId) {
|
|
36133
|
+
return this.state.get(deviceId);
|
|
36134
|
+
}
|
|
36135
|
+
/**
|
|
36136
|
+
* Seed the whole fleet ONCE with a single `getAllBindings`. Idempotent: a
|
|
36137
|
+
* warm cache is a no-op, and concurrent callers coalesce onto one read. A
|
|
36138
|
+
* failed seed leaves the mirror COLD so the next tick retries — during that
|
|
36139
|
+
* window every id answers unknown, which never reports a camera as excluded.
|
|
36140
|
+
*/
|
|
36141
|
+
async ensureWarm() {
|
|
36142
|
+
if (this.warm) return;
|
|
36143
|
+
if (this.seeding) return this.seeding;
|
|
36144
|
+
this.seeding = this.seed();
|
|
36145
|
+
try {
|
|
36146
|
+
await this.seeding;
|
|
36147
|
+
} finally {
|
|
36148
|
+
this.seeding = null;
|
|
36149
|
+
}
|
|
36150
|
+
}
|
|
36151
|
+
async seed() {
|
|
36152
|
+
try {
|
|
36153
|
+
const rows = await this.api.deviceManager.getAllBindings.query({});
|
|
36154
|
+
for (const row of rows) {
|
|
36155
|
+
if (this.state.has(row.deviceId)) continue;
|
|
36156
|
+
this.state.set(row.deviceId, hasWrappedDetection(row.entries));
|
|
36157
|
+
}
|
|
36158
|
+
this.warm = true;
|
|
36159
|
+
this.logger.info("detection-coverage mirror warm", { meta: { devices: this.state.size } });
|
|
36160
|
+
} catch (err) {
|
|
36161
|
+
this.logger.warn("detection-coverage seed failed — coverage unknown until it lands", { meta: { error: String(err) } });
|
|
36162
|
+
}
|
|
36163
|
+
}
|
|
36164
|
+
/**
|
|
36165
|
+
* A `DeviceBindingsChanged` for the detection cap re-reads THAT ONE device
|
|
36166
|
+
* (never the fleet). Fire-and-forget from the synchronous event handler.
|
|
36167
|
+
*/
|
|
36168
|
+
onBindingsChanged(event) {
|
|
36169
|
+
if (event.capName !== "detection-pipeline") return Promise.resolve();
|
|
36170
|
+
return this.refreshDevice(event.deviceId);
|
|
36171
|
+
}
|
|
36172
|
+
/** Drop a device the fleet no longer has (`device.unregistered`). */
|
|
36173
|
+
forget(deviceId) {
|
|
36174
|
+
this.state.delete(deviceId);
|
|
36175
|
+
}
|
|
36176
|
+
/** Return to cold — used on addon shutdown. */
|
|
36177
|
+
clearAll() {
|
|
36178
|
+
this.state.clear();
|
|
36179
|
+
this.warm = false;
|
|
36180
|
+
this.seeding = null;
|
|
36181
|
+
}
|
|
36182
|
+
async refreshDevice(deviceId) {
|
|
36183
|
+
try {
|
|
36184
|
+
const active = await this.readWrappedDetection(deviceId);
|
|
36185
|
+
const prev = this.state.get(deviceId);
|
|
36186
|
+
if (!active && prev === true) {
|
|
36187
|
+
if (await this.readWrappedDetection(deviceId)) {
|
|
36188
|
+
this.state.set(deviceId, true);
|
|
36189
|
+
return;
|
|
36190
|
+
}
|
|
36191
|
+
}
|
|
36192
|
+
this.state.set(deviceId, active);
|
|
36193
|
+
} catch (err) {
|
|
36194
|
+
this.logger.debug("detection-coverage refresh failed — keeping the previous value", {
|
|
36195
|
+
tags: { deviceId },
|
|
36196
|
+
meta: { error: String(err) }
|
|
36197
|
+
});
|
|
36198
|
+
}
|
|
36199
|
+
}
|
|
36200
|
+
async readWrappedDetection(deviceId) {
|
|
36201
|
+
return hasWrappedDetection((await this.api.deviceManager.getBindings.query({ deviceId })).entries);
|
|
36202
|
+
}
|
|
36203
|
+
};
|
|
36204
|
+
/** The one authority: a WRAPPED `detection-pipeline` binding is present. A
|
|
36205
|
+
* native binding of the same cap is NOT coverage (D62: absence of the WRAPPED
|
|
36206
|
+
* binding is how a deactivated wrapper is represented). */
|
|
36207
|
+
function hasWrappedDetection(entries) {
|
|
36208
|
+
return entries.some((e) => e.capName === "detection-pipeline" && e.kind === "wrapped");
|
|
36209
|
+
}
|
|
36210
|
+
//#endregion
|
|
36073
36211
|
//#region src/pipeline-analytics/runtime/capture-scheduler.ts
|
|
36074
36212
|
/**
|
|
36075
36213
|
* Kind-derived default priorities (0 = highest). Explicit `priority` wins.
|
|
@@ -41452,6 +41590,12 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
41452
41590
|
* path). Null on non-post-processing nodes or when disabled. */
|
|
41453
41591
|
embeddingDispatcher = null;
|
|
41454
41592
|
bindingCache = null;
|
|
41593
|
+
/** Alarm-coverage mirror the NC device directory reads — WHICH devices carry
|
|
41594
|
+
* a wrapped `detection-pipeline` binding. Fleet-seeded ONCE, then per-device
|
|
41595
|
+
* updates off `DeviceBindingsChanged`, so the 30 s rule-reload tick stops
|
|
41596
|
+
* sweeping `getAllBindings` over the whole fleet. See
|
|
41597
|
+
* `runtime/detection-coverage-cache.ts`. */
|
|
41598
|
+
detectionCoverage = null;
|
|
41455
41599
|
/** Per-device throttle for the inactive-binding frame-drop warn (5 min). */
|
|
41456
41600
|
inactiveBindingDropWarnAt = /* @__PURE__ */ new Map();
|
|
41457
41601
|
zoneAnalytics = null;
|
|
@@ -42635,6 +42779,10 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
42635
42779
|
api,
|
|
42636
42780
|
logger: logger.child("BindingCache")
|
|
42637
42781
|
});
|
|
42782
|
+
this.detectionCoverage = new DetectionCoverageCache({
|
|
42783
|
+
api,
|
|
42784
|
+
logger: logger.child("DetectionCoverage")
|
|
42785
|
+
});
|
|
42638
42786
|
const zoneAnalytics = new ZoneAnalyticsProvider({
|
|
42639
42787
|
logger: logger.child("ZoneAnalytics"),
|
|
42640
42788
|
onOccupancySample: (deviceId, count, at, zoneId) => this.lts?.note("occupancy", deviceId, count, at, zoneId ?? ""),
|
|
@@ -42716,17 +42864,15 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
42716
42864
|
},
|
|
42717
42865
|
listDeviceIdentities: async () => {
|
|
42718
42866
|
const all = await api.deviceManager.listAll.query({ projection: "slim" });
|
|
42719
|
-
const
|
|
42720
|
-
|
|
42721
|
-
return /* @__PURE__ */ new Map();
|
|
42722
|
-
});
|
|
42867
|
+
const coverage = this.detectionCoverage;
|
|
42868
|
+
if (coverage) await coverage.ensureWarm();
|
|
42723
42869
|
return all.map((d) => ({
|
|
42724
42870
|
deviceId: d.id,
|
|
42725
42871
|
type: d.type,
|
|
42726
42872
|
name: d.name,
|
|
42727
42873
|
isCamera: d.isCamera,
|
|
42728
42874
|
online: d.online && !d.disabled,
|
|
42729
|
-
...
|
|
42875
|
+
...coverage?.has(d.id) ? { detectionActive: coverage.get(d.id) === true } : {}
|
|
42730
42876
|
}));
|
|
42731
42877
|
},
|
|
42732
42878
|
readDeviceZoneIds: async (ids) => {
|
|
@@ -43434,6 +43580,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
43434
43580
|
* `runtime/wire-subscriptions.ts`. */
|
|
43435
43581
|
handleBindingsChangedEvent(data) {
|
|
43436
43582
|
this.bindingCache?.onBindingsChanged(data);
|
|
43583
|
+
this.detectionCoverage?.onBindingsChanged(data);
|
|
43437
43584
|
if (data.capName === "pipeline-analytics" && data.reason === "wrapper-deactivated") {
|
|
43438
43585
|
this.trackStore?.clearDevice(data.deviceId);
|
|
43439
43586
|
this.residents.closeDevice(data.deviceId);
|
|
@@ -43459,6 +43606,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
43459
43606
|
this.audioMarkers?.forgetDevice(deviceId);
|
|
43460
43607
|
this.settingsCaches.clearDevice(deviceId);
|
|
43461
43608
|
this.bindingCache?.invalidate(deviceId);
|
|
43609
|
+
this.detectionCoverage?.forget(deviceId);
|
|
43462
43610
|
this.zoneAnalytics?.forgetDevice(deviceId);
|
|
43463
43611
|
this.audioMetrics?.forgetDevice(deviceId);
|
|
43464
43612
|
this.releaseProxy(deviceId);
|
|
@@ -43664,6 +43812,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
43664
43812
|
this.trackStore?.clearAll();
|
|
43665
43813
|
this.stationaryRegistry = null;
|
|
43666
43814
|
this.bindingCache?.clearAll();
|
|
43815
|
+
this.detectionCoverage?.clearAll();
|
|
43667
43816
|
await this.eventMediaDataPlane?.dispose();
|
|
43668
43817
|
this.eventMediaDataPlane = null;
|
|
43669
43818
|
this.eventMediaBaseUrl = null;
|