@camstack/addon-post-analysis 1.2.330 → 1.2.332
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/THIRD_PARTY_MODELS.md +6 -30
- package/dist/{clip-model-registry-DQmmIYXS.js → clip-model-registry-CthiqMDB.js} +120 -254
- package/dist/{clip-model-registry-CGaaUvAD.mjs → clip-model-registry-cujWRWxP.mjs} +120 -254
- 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-BNJdXVq8.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BthiFwI_.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DxUsRAkr.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-Bj7szOak.mjs → hostInit-CR18-6F0.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +487 -183
- package/dist/pipeline-analytics/index.mjs +487 -183
- 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_ui_mf_2_library__loadShare__.js-n9xCtjjZ.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-CthiqMDB.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");
|
|
@@ -370,6 +370,20 @@ function skippedFor(deviceIds, skipProbe) {
|
|
|
370
370
|
return out;
|
|
371
371
|
}
|
|
372
372
|
/**
|
|
373
|
+
* `active` / `skipped` for the arm / arming TITLE — `(n attivi, m saltati)`.
|
|
374
|
+
*
|
|
375
|
+
* Named coverage only: an `allDevices` mode has no finite Active set, so the
|
|
376
|
+
* caller picks the `.all` title key instead of stuffing a fake number here.
|
|
377
|
+
*/
|
|
378
|
+
function armCountParams(coverage, bypassed = []) {
|
|
379
|
+
const skipped = new Set(coverage.skippedDevices.map((s) => s.deviceId));
|
|
380
|
+
const excluded = new Set(bypassed);
|
|
381
|
+
return {
|
|
382
|
+
active: `${coverage.deviceIds.filter((id) => !skipped.has(id) && !excluded.has(id)).length}`,
|
|
383
|
+
skipped: `${skipped.size}`
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
373
387
|
* Whether this mode arms anything, and if not, which way it fails.
|
|
374
388
|
*
|
|
375
389
|
* An `allDevices` mode is never a gap: `skippedDevices` only ever probes the
|
|
@@ -384,28 +398,13 @@ function alarmCoverageGap(coverage) {
|
|
|
384
398
|
if (coverage.skippedDevices.length < coverage.deviceIds.length) return null;
|
|
385
399
|
return "all-skipped";
|
|
386
400
|
}
|
|
401
|
+
/** Device names as a lock-screen bullet list (one name per line). */
|
|
402
|
+
function bulletDeviceNames(names) {
|
|
403
|
+
return names.map((n) => `• ${n}`).join("\n");
|
|
404
|
+
}
|
|
387
405
|
/**
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
* A LIST, not a sentence. The mode is in the title and the body is one
|
|
391
|
-
* `Label: names` line per device group, because this is read on a lock screen
|
|
392
|
-
* and the question is always "which one" — the prose version ("3 dispositivi
|
|
393
|
-
* attivi: …. Esclusi: ….") made the operator parse a paragraph to find a
|
|
394
|
-
* window. Each variant still owns its own catalog key, so the Texts tab can
|
|
395
|
-
* re-word "nothing is armed" without touching "these are armed".
|
|
396
|
-
*
|
|
397
|
-
* Names, not ids: this is read by a person standing at a door. An id that has
|
|
398
|
-
* no name falls back to a NAMED, translated placeholder rather than being
|
|
399
|
-
* dropped — a silently shorter list would understate what is armed, which is
|
|
400
|
-
* the one error this message must not make. (It used to hard-code the English
|
|
401
|
-
* `Device <id>` into both languages.)
|
|
402
|
-
*
|
|
403
|
-
* **Always a sentence.** It used to return null for a mode no rule is gated on,
|
|
404
|
-
* on the grounds that "Night armed" followed by an unwatched night is a false
|
|
405
|
-
* assurance — true, but the caller then emitted `alarm-armed` anyway with a
|
|
406
|
-
* fallback body key that existed in NEITHER locale, so the operator received a
|
|
407
|
-
* title and an empty line. Silence is not the third option here: SAYING that
|
|
408
|
-
* the mode arms nothing is.
|
|
406
|
+
* Skipped / excluded devices FIRST, then active — each name on its own
|
|
407
|
+
* `•` line. The mode is in the title; the body answers "which ones".
|
|
409
408
|
*/
|
|
410
409
|
function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
411
410
|
const rules = `${coverage.ruleCount}`;
|
|
@@ -414,7 +413,11 @@ function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
|
414
413
|
const gap = alarmCoverageGap(coverage);
|
|
415
414
|
if (gap === "uncovered") return {
|
|
416
415
|
bodyKey: "alarm.arm.body.uncovered",
|
|
417
|
-
textParams: {
|
|
416
|
+
textParams: {
|
|
417
|
+
mode,
|
|
418
|
+
active: "0",
|
|
419
|
+
skipped: "0"
|
|
420
|
+
},
|
|
418
421
|
textCount: 0
|
|
419
422
|
};
|
|
420
423
|
if (gap === "all-skipped") {
|
|
@@ -425,7 +428,9 @@ function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
|
425
428
|
rules,
|
|
426
429
|
mode,
|
|
427
430
|
devices: `${names.length}`,
|
|
428
|
-
names: names
|
|
431
|
+
names: bulletDeviceNames(names),
|
|
432
|
+
active: "0",
|
|
433
|
+
skipped: `${names.length}`
|
|
429
434
|
},
|
|
430
435
|
textCount: names.length
|
|
431
436
|
};
|
|
@@ -438,9 +443,10 @@ function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
|
438
443
|
rules,
|
|
439
444
|
mode,
|
|
440
445
|
count: `${names.length}`,
|
|
441
|
-
names: names
|
|
446
|
+
names: bulletDeviceNames(names)
|
|
442
447
|
},
|
|
443
|
-
textCount: names.length
|
|
448
|
+
textCount: names.length,
|
|
449
|
+
titleKeySuffix: "all"
|
|
444
450
|
};
|
|
445
451
|
}
|
|
446
452
|
if (coverage.allDevices) return {
|
|
@@ -449,25 +455,30 @@ function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
|
449
455
|
rules,
|
|
450
456
|
mode
|
|
451
457
|
},
|
|
452
|
-
textCount: coverage.ruleCount
|
|
458
|
+
textCount: coverage.ruleCount,
|
|
459
|
+
titleKeySuffix: "all"
|
|
453
460
|
};
|
|
454
461
|
if (coverage.deviceIds.length === 0) return {
|
|
455
462
|
bodyKey: "alarm.arm.body.noDevices",
|
|
456
463
|
textParams: {
|
|
457
464
|
rules,
|
|
458
|
-
mode
|
|
465
|
+
mode,
|
|
466
|
+
active: "0",
|
|
467
|
+
skipped: "0"
|
|
459
468
|
},
|
|
460
469
|
textCount: coverage.ruleCount
|
|
461
470
|
};
|
|
462
471
|
const skipped = new Set(coverage.skippedDevices.map((s) => s.deviceId));
|
|
463
472
|
const names = coverage.deviceIds.filter((id) => !skipped.has(id)).map(deviceName);
|
|
473
|
+
const counts = armCountParams(coverage);
|
|
464
474
|
if (skipped.size === 0) return {
|
|
465
475
|
bodyKey: "alarm.arm.body.devices",
|
|
466
476
|
textParams: {
|
|
467
477
|
rules,
|
|
468
478
|
mode,
|
|
469
479
|
devices: `${names.length}`,
|
|
470
|
-
names: names
|
|
480
|
+
names: bulletDeviceNames(names),
|
|
481
|
+
...counts
|
|
471
482
|
},
|
|
472
483
|
textCount: names.length
|
|
473
484
|
};
|
|
@@ -477,8 +488,9 @@ function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
|
477
488
|
rules,
|
|
478
489
|
mode,
|
|
479
490
|
devices: `${names.length}`,
|
|
480
|
-
names: names
|
|
481
|
-
skippedNames: coverage.skippedDevices.map((s) => deviceName(s.deviceId))
|
|
491
|
+
names: bulletDeviceNames(names),
|
|
492
|
+
skippedNames: bulletDeviceNames(coverage.skippedDevices.map((s) => deviceName(s.deviceId))),
|
|
493
|
+
...counts
|
|
482
494
|
},
|
|
483
495
|
textCount: names.length
|
|
484
496
|
};
|
|
@@ -493,7 +505,7 @@ function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
|
493
505
|
function armedWithBypass(coverage, bypassed, deviceName) {
|
|
494
506
|
const skipped = new Set(coverage.skippedDevices.map((s) => s.deviceId));
|
|
495
507
|
const excluded = new Set(bypassed);
|
|
496
|
-
const bypassedNames = bypassed.map(deviceName)
|
|
508
|
+
const bypassedNames = bulletDeviceNames(bypassed.map(deviceName));
|
|
497
509
|
if (coverage.allDevices) return {
|
|
498
510
|
bodyKey: "alarm.arm.body.bypassedAll",
|
|
499
511
|
textParams: {
|
|
@@ -501,10 +513,12 @@ function armedWithBypass(coverage, bypassed, deviceName) {
|
|
|
501
513
|
mode: coverage.mode,
|
|
502
514
|
bypassedNames
|
|
503
515
|
},
|
|
504
|
-
textCount: coverage.ruleCount
|
|
516
|
+
textCount: coverage.ruleCount,
|
|
517
|
+
titleKeySuffix: "all"
|
|
505
518
|
};
|
|
506
519
|
const armedIds = coverage.deviceIds.filter((id) => !skipped.has(id) && !excluded.has(id));
|
|
507
|
-
const names = armedIds.map(deviceName)
|
|
520
|
+
const names = bulletDeviceNames(armedIds.map(deviceName));
|
|
521
|
+
const counts = armCountParams(coverage, bypassed);
|
|
508
522
|
return {
|
|
509
523
|
bodyKey: "alarm.arm.body.bypassed",
|
|
510
524
|
textParams: {
|
|
@@ -512,7 +526,8 @@ function armedWithBypass(coverage, bypassed, deviceName) {
|
|
|
512
526
|
mode: coverage.mode,
|
|
513
527
|
devices: `${armedIds.length}`,
|
|
514
528
|
names,
|
|
515
|
-
bypassedNames
|
|
529
|
+
bypassedNames,
|
|
530
|
+
...counts
|
|
516
531
|
},
|
|
517
532
|
textCount: armedIds.length
|
|
518
533
|
};
|
|
@@ -533,69 +548,159 @@ function buildArmRefusedAnnouncement(mode, names, withArmAnyway) {
|
|
|
533
548
|
textParams: {
|
|
534
549
|
mode,
|
|
535
550
|
devices: `${names.length}`,
|
|
536
|
-
names: names
|
|
551
|
+
names: bulletDeviceNames(names)
|
|
537
552
|
},
|
|
538
553
|
textCount: names.length
|
|
539
554
|
};
|
|
540
555
|
}
|
|
541
556
|
/**
|
|
542
|
-
* The
|
|
557
|
+
* The same coverage lists as {@link buildArmAnnouncement}, said at second 0.
|
|
543
558
|
*
|
|
544
|
-
* The
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
* **It never repeats the device list.** The countdown lives in the TITLE
|
|
551
|
-
* (`system.alarm-arming.title.<mode>` renders `{{seconds}}`), and the armed
|
|
552
|
-
* list arrives thirty seconds later in the arm announcement — printing it
|
|
553
|
-
* twice is the verbosity that made the operator stop reading both. So the body
|
|
554
|
-
* is what is WRONG: what was bypassed, what is excluded, or that nothing is.
|
|
559
|
+
* The countdown lives in the TITLE (`system.alarm-arming.title.<mode>` renders
|
|
560
|
+
* `{{seconds}}`); the body is the labelled device lists — Active and Skipped
|
|
561
|
+
* (and Bypassed when the operator confirmed an open sensor). Saying the lists
|
|
562
|
+
* only at second 30 left the exit-delay push as "No exclusions" / a lone
|
|
563
|
+
* Esclusi line, which is exactly when the operator can still disarm and fix
|
|
564
|
+
* a mute or an offline camera.
|
|
555
565
|
*
|
|
556
566
|
* `seconds` stays in the params because the title asks for it, and `textCount`
|
|
557
567
|
* stays with it: a row enqueued before this deploy still pluralises on the
|
|
558
568
|
* countdown it was written for.
|
|
559
569
|
*/
|
|
560
570
|
function buildArmingAnnouncement(coverage, seconds, deviceName, bypassed = []) {
|
|
571
|
+
const mode = coverage.mode;
|
|
572
|
+
const rules = `${coverage.ruleCount}`;
|
|
561
573
|
const textParams = {
|
|
562
|
-
mode
|
|
574
|
+
mode,
|
|
563
575
|
seconds: `${seconds}`
|
|
564
576
|
};
|
|
565
|
-
if (bypassed.length > 0) return
|
|
566
|
-
|
|
577
|
+
if (bypassed.length > 0) return armingWithBypass(coverage, seconds, bypassed, deviceName);
|
|
578
|
+
const gap = alarmCoverageGap(coverage);
|
|
579
|
+
if (gap === "uncovered") return {
|
|
580
|
+
bodyKey: "alarm.arming.body.uncovered",
|
|
567
581
|
textParams: {
|
|
568
582
|
...textParams,
|
|
569
|
-
|
|
583
|
+
active: "0",
|
|
584
|
+
skipped: "0"
|
|
570
585
|
},
|
|
571
586
|
textCount: seconds
|
|
572
587
|
};
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
588
|
+
if (gap === "all-skipped") {
|
|
589
|
+
const names = coverage.skippedDevices.map((s) => deviceName(s.deviceId));
|
|
590
|
+
return {
|
|
591
|
+
bodyKey: "alarm.arming.body.allSkipped",
|
|
592
|
+
textParams: {
|
|
593
|
+
...textParams,
|
|
594
|
+
rules,
|
|
595
|
+
devices: `${names.length}`,
|
|
596
|
+
names: bulletDeviceNames(names),
|
|
597
|
+
active: "0",
|
|
598
|
+
skipped: `${names.length}`
|
|
599
|
+
},
|
|
600
|
+
textCount: seconds
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
if (coverage.allDevices && coverage.allDevicesExcept.length > 0) {
|
|
604
|
+
const names = coverage.allDevicesExcept.map(deviceName);
|
|
605
|
+
return {
|
|
606
|
+
bodyKey: "alarm.arming.body.allDevicesExcept",
|
|
607
|
+
textParams: {
|
|
608
|
+
...textParams,
|
|
609
|
+
rules,
|
|
610
|
+
count: `${names.length}`,
|
|
611
|
+
names: bulletDeviceNames(names)
|
|
612
|
+
},
|
|
613
|
+
textCount: seconds,
|
|
614
|
+
titleKeySuffix: "all"
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
if (coverage.allDevices) return {
|
|
618
|
+
bodyKey: "alarm.arming.body.allDevices",
|
|
619
|
+
textParams: {
|
|
620
|
+
...textParams,
|
|
621
|
+
rules
|
|
622
|
+
},
|
|
623
|
+
textCount: seconds,
|
|
624
|
+
titleKeySuffix: "all"
|
|
579
625
|
};
|
|
580
|
-
if (
|
|
581
|
-
bodyKey: "alarm.arming.body.
|
|
626
|
+
if (coverage.deviceIds.length === 0) return {
|
|
627
|
+
bodyKey: "alarm.arming.body.noDevices",
|
|
582
628
|
textParams: {
|
|
583
629
|
...textParams,
|
|
584
|
-
|
|
630
|
+
rules,
|
|
631
|
+
active: "0",
|
|
632
|
+
skipped: "0"
|
|
585
633
|
},
|
|
586
634
|
textCount: seconds
|
|
587
635
|
};
|
|
588
|
-
|
|
589
|
-
|
|
636
|
+
const skipped = new Set(coverage.skippedDevices.map((s) => s.deviceId));
|
|
637
|
+
const names = coverage.deviceIds.filter((id) => !skipped.has(id)).map(deviceName);
|
|
638
|
+
const counts = armCountParams(coverage);
|
|
639
|
+
if (skipped.size === 0) return {
|
|
640
|
+
bodyKey: "alarm.arming.body.devices",
|
|
590
641
|
textParams: {
|
|
591
642
|
...textParams,
|
|
592
|
-
|
|
643
|
+
rules,
|
|
644
|
+
devices: `${names.length}`,
|
|
645
|
+
names: bulletDeviceNames(names),
|
|
646
|
+
...counts
|
|
593
647
|
},
|
|
594
|
-
textCount:
|
|
648
|
+
textCount: seconds
|
|
595
649
|
};
|
|
596
650
|
return {
|
|
597
|
-
bodyKey: "alarm.arming.body",
|
|
651
|
+
bodyKey: "alarm.arming.body.devicesSkipped",
|
|
652
|
+
textParams: {
|
|
653
|
+
...textParams,
|
|
654
|
+
rules,
|
|
655
|
+
devices: `${names.length}`,
|
|
656
|
+
names: bulletDeviceNames(names),
|
|
657
|
+
skippedNames: bulletDeviceNames(coverage.skippedDevices.map((s) => deviceName(s.deviceId))),
|
|
658
|
+
...counts
|
|
659
|
+
},
|
|
660
|
+
textCount: seconds
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* "Attivi: Porta retro, Cancello — Bypassati: Finestra veranda" while the exit
|
|
665
|
+
* delay still runs — same halves as {@link armedWithBypass}, with `seconds`
|
|
666
|
+
* frozen for the title.
|
|
667
|
+
*/
|
|
668
|
+
function armingWithBypass(coverage, seconds, bypassed, deviceName) {
|
|
669
|
+
const skipped = new Set(coverage.skippedDevices.map((s) => s.deviceId));
|
|
670
|
+
const excluded = new Set(bypassed);
|
|
671
|
+
const bypassedNames = bulletDeviceNames(bypassed.map(deviceName));
|
|
672
|
+
const textParams = {
|
|
673
|
+
mode: coverage.mode,
|
|
674
|
+
seconds: `${seconds}`,
|
|
675
|
+
rules: `${coverage.ruleCount}`,
|
|
676
|
+
bypassedNames
|
|
677
|
+
};
|
|
678
|
+
if (coverage.allDevices) return {
|
|
679
|
+
bodyKey: "alarm.arming.body.bypassedAll",
|
|
598
680
|
textParams,
|
|
681
|
+
textCount: seconds,
|
|
682
|
+
titleKeySuffix: "all"
|
|
683
|
+
};
|
|
684
|
+
const armedIds = coverage.deviceIds.filter((id) => !skipped.has(id) && !excluded.has(id));
|
|
685
|
+
const counts = armCountParams(coverage, bypassed);
|
|
686
|
+
if (armedIds.length === 0) return {
|
|
687
|
+
bodyKey: "alarm.arming.body.bypassedOnly",
|
|
688
|
+
textParams: {
|
|
689
|
+
mode: coverage.mode,
|
|
690
|
+
seconds: `${seconds}`,
|
|
691
|
+
bypassedNames,
|
|
692
|
+
...counts
|
|
693
|
+
},
|
|
694
|
+
textCount: seconds
|
|
695
|
+
};
|
|
696
|
+
return {
|
|
697
|
+
bodyKey: "alarm.arming.body.bypassed",
|
|
698
|
+
textParams: {
|
|
699
|
+
...textParams,
|
|
700
|
+
devices: `${armedIds.length}`,
|
|
701
|
+
names: bulletDeviceNames(armedIds.map(deviceName)),
|
|
702
|
+
...counts
|
|
703
|
+
},
|
|
599
704
|
textCount: seconds
|
|
600
705
|
};
|
|
601
706
|
}
|
|
@@ -3218,12 +3323,23 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
3218
3323
|
* dropped it anyway, so the exclusion is not what ignored it.
|
|
3219
3324
|
*/
|
|
3220
3325
|
announceIgnoredTrigger(deviceId, now) {
|
|
3326
|
+
const state = stateAt(this.machine, now);
|
|
3221
3327
|
if (!isArmed(this.machine, now)) {
|
|
3222
3328
|
this.ctx.logger.debug("alarm bypass not announced — the panel is not armed yet", {
|
|
3223
3329
|
tags: { deviceId },
|
|
3224
3330
|
meta: {
|
|
3225
3331
|
panelDeviceId: this.id,
|
|
3226
|
-
state
|
|
3332
|
+
state
|
|
3333
|
+
}
|
|
3334
|
+
});
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
if (state === "triggered") {
|
|
3338
|
+
this.ctx.logger.debug("alarm bypass not announced — the siren is already sounding", {
|
|
3339
|
+
tags: { deviceId },
|
|
3340
|
+
meta: {
|
|
3341
|
+
panelDeviceId: this.id,
|
|
3342
|
+
state
|
|
3227
3343
|
}
|
|
3228
3344
|
});
|
|
3229
3345
|
return;
|
|
@@ -5929,7 +6045,7 @@ function isAdoptable(record, nowMs) {
|
|
|
5929
6045
|
* what `localDayMinute` does for the same input — one behaviour for a bad
|
|
5930
6046
|
* zone, not two.
|
|
5931
6047
|
*/
|
|
5932
|
-
function clockOf$
|
|
6048
|
+
function clockOf$6(atMs, timeZone) {
|
|
5933
6049
|
if (timeZone !== void 0) try {
|
|
5934
6050
|
return new Intl.DateTimeFormat("en-GB", {
|
|
5935
6051
|
timeZone,
|
|
@@ -6065,7 +6181,7 @@ var TimelapseRenderer = class {
|
|
|
6065
6181
|
includeAudio: false,
|
|
6066
6182
|
maxLifeMs: await this.ports.exportLifeMs(),
|
|
6067
6183
|
deleteAfterDownload: false,
|
|
6068
|
-
title: `${request.ruleName} ${clockOf$
|
|
6184
|
+
title: `${request.ruleName} ${clockOf$6(window.startMs, request.timezone)}→${clockOf$6(window.endMs, request.timezone)}`,
|
|
6069
6185
|
timelapse: {
|
|
6070
6186
|
everyMs,
|
|
6071
6187
|
outputFps: request.framerate,
|
|
@@ -6396,7 +6512,7 @@ var TimelapseRenderer = class {
|
|
|
6396
6512
|
...meta,
|
|
6397
6513
|
unservedWindowCount: verdict.unservedWindows.length,
|
|
6398
6514
|
unservedWindows: verdict.unservedWindows.slice(0, 12).map((w) => ({
|
|
6399
|
-
at: clockOf$
|
|
6515
|
+
at: clockOf$6(request.window.startMs + w.fromSec * 1e3, request.timezone),
|
|
6400
6516
|
fromSec: Math.round(w.fromSec),
|
|
6401
6517
|
toSec: Math.round(w.toSec),
|
|
6402
6518
|
expectedDenseFrames: w.expectedDenseFrames,
|
|
@@ -13064,7 +13180,8 @@ function alarmTextKeys(transition, body) {
|
|
|
13064
13180
|
const { kind, source } = transition;
|
|
13065
13181
|
const combined = combinedAlarmText(kind, source);
|
|
13066
13182
|
if (combined !== null) return combined;
|
|
13067
|
-
const
|
|
13183
|
+
const baseTitle = transition.mode !== void 0 ? `system.${kind}.title.${transition.mode}` : `system.${kind}.title`;
|
|
13184
|
+
const titleKey = body?.titleKeySuffix !== void 0 ? `${baseTitle}.${body.titleKeySuffix}` : baseTitle;
|
|
13068
13185
|
if (body !== void 0) return {
|
|
13069
13186
|
titleKey,
|
|
13070
13187
|
bodyKey: body.bodyKey
|
|
@@ -14771,34 +14888,47 @@ var en_default = {
|
|
|
14771
14888
|
"snooze.scope.anyRule": "A rule",
|
|
14772
14889
|
"snooze.scope.rule": "“{{rule}}”",
|
|
14773
14890
|
"alarm.arm.body.allDevices": "Active: all devices",
|
|
14774
|
-
"alarm.arm.body.allDevicesExcept": "Active: all devices except
|
|
14891
|
+
"alarm.arm.body.allDevicesExcept": "Active: all devices except\n{{names}}",
|
|
14775
14892
|
"alarm.arm.body.noDevices": "Active: no device restriction",
|
|
14776
|
-
"alarm.arm.body.devices": "Active
|
|
14777
|
-
"alarm.arm.body.devicesSkipped": "
|
|
14893
|
+
"alarm.arm.body.devices": "Active:\n{{names}}",
|
|
14894
|
+
"alarm.arm.body.devicesSkipped": "Excluded:\n{{skippedNames}}\nActive:\n{{names}}",
|
|
14778
14895
|
"alarm.arm.body.uncovered": "Active: none — no rule uses this mode",
|
|
14779
|
-
"alarm.arm.body.allSkipped": "
|
|
14896
|
+
"alarm.arm.body.allSkipped": "Excluded:\n{{names}}\nActive: none",
|
|
14780
14897
|
"alarm.arming.body": "No exclusions",
|
|
14898
|
+
"alarm.arming.body.allDevices": "Active: all devices",
|
|
14899
|
+
"alarm.arming.body.allDevicesExcept": "Active: all devices except\n{{names}}",
|
|
14900
|
+
"alarm.arming.body.noDevices": "Active: no device restriction",
|
|
14901
|
+
"alarm.arming.body.devices": "Active:\n{{names}}",
|
|
14902
|
+
"alarm.arming.body.devicesSkipped": "Excluded:\n{{skippedNames}}\nActive:\n{{names}}",
|
|
14781
14903
|
"alarm.arming.body.uncovered": "Active: none — no rule uses this mode",
|
|
14782
|
-
"alarm.arming.body.allSkipped": "
|
|
14783
|
-
"alarm.arming.body.skipped": "Excluded
|
|
14784
|
-
"alarm.arm.body.bypassed": "
|
|
14785
|
-
"alarm.arm.body.bypassedAll": "
|
|
14786
|
-
"alarm.arming.body.bypassed": "Bypassed
|
|
14787
|
-
"alarm.
|
|
14788
|
-
"alarm.
|
|
14904
|
+
"alarm.arming.body.allSkipped": "Excluded:\n{{names}}\nActive: none",
|
|
14905
|
+
"alarm.arming.body.skipped": "Excluded:\n{{names}}",
|
|
14906
|
+
"alarm.arm.body.bypassed": "Bypassed:\n{{bypassedNames}}\nActive:\n{{names}}",
|
|
14907
|
+
"alarm.arm.body.bypassedAll": "Bypassed:\n{{bypassedNames}}\nActive: all devices",
|
|
14908
|
+
"alarm.arming.body.bypassed": "Bypassed:\n{{bypassedNames}}\nActive:\n{{names}}",
|
|
14909
|
+
"alarm.arming.body.bypassedAll": "Bypassed:\n{{bypassedNames}}\nActive: all devices",
|
|
14910
|
+
"alarm.arming.body.bypassedOnly": "Bypassed:\n{{bypassedNames}}",
|
|
14911
|
+
"alarm.armRefused.body": "Open:\n{{names}}",
|
|
14912
|
+
"alarm.armRefused.body.hint": "Open:\n{{names}}\nTap “Arm anyway” to arm with them excluded.",
|
|
14789
14913
|
"alarm.device.unnamed": "Device {{id}}",
|
|
14790
14914
|
"alarm.combined.title": "Alarm: {{deviceName}}",
|
|
14791
14915
|
"system.alarm-arm-refused.title.home": "Arming home refused",
|
|
14792
14916
|
"system.alarm-arm-refused.title.away": "Arming away refused",
|
|
14793
14917
|
"system.alarm-arm-refused.title.night": "Arming night refused",
|
|
14794
14918
|
"system.alarm-arm-refused.body": "Arming refused",
|
|
14795
|
-
"system.alarm-arming.title.home": "Arming home — {{seconds}}s",
|
|
14796
|
-
"system.alarm-arming.title.away": "Arming away — {{seconds}}s",
|
|
14797
|
-
"system.alarm-arming.title.night": "Arming night — {{seconds}}s",
|
|
14919
|
+
"system.alarm-arming.title.home": "Arming home — {{seconds}}s ({{active}} active, {{skipped}} skipped)",
|
|
14920
|
+
"system.alarm-arming.title.away": "Arming away — {{seconds}}s ({{active}} active, {{skipped}} skipped)",
|
|
14921
|
+
"system.alarm-arming.title.night": "Arming night — {{seconds}}s ({{active}} active, {{skipped}} skipped)",
|
|
14922
|
+
"system.alarm-arming.title.home.all": "Arming home — {{seconds}}s",
|
|
14923
|
+
"system.alarm-arming.title.away.all": "Arming away — {{seconds}}s",
|
|
14924
|
+
"system.alarm-arming.title.night.all": "Arming night — {{seconds}}s",
|
|
14798
14925
|
"system.alarm-arming.body": "Arming",
|
|
14799
|
-
"system.alarm-armed.title.home": "Alarm
|
|
14800
|
-
"system.alarm-armed.title.away": "Alarm
|
|
14801
|
-
"system.alarm-armed.title.night": "Alarm
|
|
14926
|
+
"system.alarm-armed.title.home": "Alarm active: home ({{active}} active, {{skipped}} skipped)",
|
|
14927
|
+
"system.alarm-armed.title.away": "Alarm active: away ({{active}} active, {{skipped}} skipped)",
|
|
14928
|
+
"system.alarm-armed.title.night": "Alarm active: night ({{active}} active, {{skipped}} skipped)",
|
|
14929
|
+
"system.alarm-armed.title.home.all": "Alarm active: home (all)",
|
|
14930
|
+
"system.alarm-armed.title.away.all": "Alarm active: away (all)",
|
|
14931
|
+
"system.alarm-armed.title.night.all": "Alarm active: night (all)",
|
|
14802
14932
|
"system.alarm-armed.body": "Alarm armed",
|
|
14803
14933
|
"system.alarm-disarmed.title": "Disarmed",
|
|
14804
14934
|
"system.alarm-disarmed.body": "Alarm disarmed",
|
|
@@ -14896,7 +15026,16 @@ var en_default = {
|
|
|
14896
15026
|
"summary.line.update": "{{packageName}} {{currentVersion}} → {{latestVersion}}",
|
|
14897
15027
|
"summary.line.update.onNode": "{{packageName}} {{currentVersion}} → {{latestVersion}} on {{nodeId}}",
|
|
14898
15028
|
"summary.line.wrapper-update": "Container {{currentVersion}} → {{latestVersion}}",
|
|
14899
|
-
"summary.line.tank-unread": "{{deviceName}} — the {{tank}} tank needs attention"
|
|
15029
|
+
"summary.line.tank-unread": "{{deviceName}} — the {{tank}} tank needs attention",
|
|
15030
|
+
"calendar-events.title": "{{rule}}: {{count}} upcoming",
|
|
15031
|
+
"calendar-events.empty": "No events in the next {{horizonDays}} day(s).",
|
|
15032
|
+
"calendar-events.day.today": "Today — {{date}}",
|
|
15033
|
+
"calendar-events.day.tomorrow": "★ Tomorrow — {{date}}",
|
|
15034
|
+
"calendar-events.day.other": "{{date}}",
|
|
15035
|
+
"calendar-events.line.timed": "• {{when}} {{title}}{{where}}",
|
|
15036
|
+
"calendar-events.line.allDay": "• All day {{title}}{{where}}",
|
|
15037
|
+
"calendar-events.where": " @ {{location}}",
|
|
15038
|
+
"calendar-events.more": "…and {{count}} more"
|
|
14900
15039
|
};
|
|
14901
15040
|
//#endregion
|
|
14902
15041
|
//#region src/notification-center/texts/it.json
|
|
@@ -15100,34 +15239,47 @@ var it_default = {
|
|
|
15100
15239
|
"snooze.scope.anyRule": "Una regola",
|
|
15101
15240
|
"snooze.scope.rule": "“{{rule}}”",
|
|
15102
15241
|
"alarm.arm.body.allDevices": "Attivi: tutti i dispositivi",
|
|
15103
|
-
"alarm.arm.body.allDevicesExcept": "Attivi: tutti i dispositivi tranne
|
|
15242
|
+
"alarm.arm.body.allDevicesExcept": "Attivi: tutti i dispositivi tranne\n{{names}}",
|
|
15104
15243
|
"alarm.arm.body.noDevices": "Attivi: nessuna restrizione",
|
|
15105
|
-
"alarm.arm.body.devices": "Attivi
|
|
15106
|
-
"alarm.arm.body.devicesSkipped": "
|
|
15244
|
+
"alarm.arm.body.devices": "Attivi:\n{{names}}",
|
|
15245
|
+
"alarm.arm.body.devicesSkipped": "Esclusi:\n{{skippedNames}}\nAttivi:\n{{names}}",
|
|
15107
15246
|
"alarm.arm.body.uncovered": "Attivi: nessuno — nessuna regola usa questa modalità",
|
|
15108
|
-
"alarm.arm.body.allSkipped": "
|
|
15247
|
+
"alarm.arm.body.allSkipped": "Esclusi:\n{{names}}\nAttivi: nessuno",
|
|
15109
15248
|
"alarm.arming.body": "Nessuna esclusione",
|
|
15249
|
+
"alarm.arming.body.allDevices": "Attivi: tutti i dispositivi",
|
|
15250
|
+
"alarm.arming.body.allDevicesExcept": "Attivi: tutti i dispositivi tranne\n{{names}}",
|
|
15251
|
+
"alarm.arming.body.noDevices": "Attivi: nessuna restrizione",
|
|
15252
|
+
"alarm.arming.body.devices": "Attivi:\n{{names}}",
|
|
15253
|
+
"alarm.arming.body.devicesSkipped": "Esclusi:\n{{skippedNames}}\nAttivi:\n{{names}}",
|
|
15110
15254
|
"alarm.arming.body.uncovered": "Attivi: nessuno — nessuna regola usa questa modalità",
|
|
15111
|
-
"alarm.arming.body.allSkipped": "
|
|
15112
|
-
"alarm.arming.body.skipped": "Esclusi
|
|
15113
|
-
"alarm.arm.body.bypassed": "
|
|
15114
|
-
"alarm.arm.body.bypassedAll": "
|
|
15115
|
-
"alarm.arming.body.bypassed": "Bypassati
|
|
15116
|
-
"alarm.
|
|
15117
|
-
"alarm.
|
|
15255
|
+
"alarm.arming.body.allSkipped": "Esclusi:\n{{names}}\nAttivi: nessuno",
|
|
15256
|
+
"alarm.arming.body.skipped": "Esclusi:\n{{names}}",
|
|
15257
|
+
"alarm.arm.body.bypassed": "Bypassati:\n{{bypassedNames}}\nAttivi:\n{{names}}",
|
|
15258
|
+
"alarm.arm.body.bypassedAll": "Bypassati:\n{{bypassedNames}}\nAttivi: tutti i dispositivi",
|
|
15259
|
+
"alarm.arming.body.bypassed": "Bypassati:\n{{bypassedNames}}\nAttivi:\n{{names}}",
|
|
15260
|
+
"alarm.arming.body.bypassedAll": "Bypassati:\n{{bypassedNames}}\nAttivi: tutti i dispositivi",
|
|
15261
|
+
"alarm.arming.body.bypassedOnly": "Bypassati:\n{{bypassedNames}}",
|
|
15262
|
+
"alarm.armRefused.body": "Aperti:\n{{names}}",
|
|
15263
|
+
"alarm.armRefused.body.hint": "Aperti:\n{{names}}\nTocca “Arma comunque” per armare escludendoli.",
|
|
15118
15264
|
"alarm.device.unnamed": "Dispositivo {{id}}",
|
|
15119
15265
|
"alarm.combined.title": "Allarme: {{deviceName}}",
|
|
15120
15266
|
"system.alarm-arm-refused.title.home": "Inserimento Casa rifiutato",
|
|
15121
15267
|
"system.alarm-arm-refused.title.away": "Inserimento Fuori rifiutato",
|
|
15122
15268
|
"system.alarm-arm-refused.title.night": "Inserimento Notte rifiutato",
|
|
15123
15269
|
"system.alarm-arm-refused.body": "Inserimento rifiutato",
|
|
15124
|
-
"system.alarm-arming.title.home": "Inserimento Casa — {{seconds}}s",
|
|
15125
|
-
"system.alarm-arming.title.away": "Inserimento Fuori — {{seconds}}s",
|
|
15126
|
-
"system.alarm-arming.title.night": "Inserimento Notte — {{seconds}}s",
|
|
15270
|
+
"system.alarm-arming.title.home": "Inserimento Casa — {{seconds}}s ({{active}} attivi, {{skipped}} saltati)",
|
|
15271
|
+
"system.alarm-arming.title.away": "Inserimento Fuori — {{seconds}}s ({{active}} attivi, {{skipped}} saltati)",
|
|
15272
|
+
"system.alarm-arming.title.night": "Inserimento Notte — {{seconds}}s ({{active}} attivi, {{skipped}} saltati)",
|
|
15273
|
+
"system.alarm-arming.title.home.all": "Inserimento Casa — {{seconds}}s",
|
|
15274
|
+
"system.alarm-arming.title.away.all": "Inserimento Fuori — {{seconds}}s",
|
|
15275
|
+
"system.alarm-arming.title.night.all": "Inserimento Notte — {{seconds}}s",
|
|
15127
15276
|
"system.alarm-arming.body": "Inserimento in corso",
|
|
15128
|
-
"system.alarm-armed.title.home": "Allarme
|
|
15129
|
-
"system.alarm-armed.title.away": "Allarme
|
|
15130
|
-
"system.alarm-armed.title.night": "Allarme
|
|
15277
|
+
"system.alarm-armed.title.home": "Allarme attivo: Casa ({{active}} attivi, {{skipped}} saltati)",
|
|
15278
|
+
"system.alarm-armed.title.away": "Allarme attivo: Fuori ({{active}} attivi, {{skipped}} saltati)",
|
|
15279
|
+
"system.alarm-armed.title.night": "Allarme attivo: Notte ({{active}} attivi, {{skipped}} saltati)",
|
|
15280
|
+
"system.alarm-armed.title.home.all": "Allarme attivo: Casa (tutti)",
|
|
15281
|
+
"system.alarm-armed.title.away.all": "Allarme attivo: Fuori (tutti)",
|
|
15282
|
+
"system.alarm-armed.title.night.all": "Allarme attivo: Notte (tutti)",
|
|
15131
15283
|
"system.alarm-armed.body": "Allarme inserito",
|
|
15132
15284
|
"system.alarm-disarmed.title": "Disinserito",
|
|
15133
15285
|
"system.alarm-disarmed.body": "Allarme disinserito",
|
|
@@ -15225,7 +15377,16 @@ var it_default = {
|
|
|
15225
15377
|
"summary.line.update": "{{packageName}} {{currentVersion}} → {{latestVersion}}",
|
|
15226
15378
|
"summary.line.update.onNode": "{{packageName}} {{currentVersion}} → {{latestVersion}} su {{nodeId}}",
|
|
15227
15379
|
"summary.line.wrapper-update": "Container {{currentVersion}} → {{latestVersion}}",
|
|
15228
|
-
"summary.line.tank-unread": "{{deviceName}} — il serbatoio {{tank}} richiede attenzione"
|
|
15380
|
+
"summary.line.tank-unread": "{{deviceName}} — il serbatoio {{tank}} richiede attenzione",
|
|
15381
|
+
"calendar-events.title": "{{rule}}: {{count}} in arrivo",
|
|
15382
|
+
"calendar-events.empty": "Nessun evento nei prossimi {{horizonDays}} giorno/i.",
|
|
15383
|
+
"calendar-events.day.today": "Oggi — {{date}}",
|
|
15384
|
+
"calendar-events.day.tomorrow": "★ Domani — {{date}}",
|
|
15385
|
+
"calendar-events.day.other": "{{date}}",
|
|
15386
|
+
"calendar-events.line.timed": "• {{when}} {{title}}{{where}}",
|
|
15387
|
+
"calendar-events.line.allDay": "• Tutto il giorno {{title}}{{where}}",
|
|
15388
|
+
"calendar-events.where": " @ {{location}}",
|
|
15389
|
+
"calendar-events.more": "…e altri {{count}}"
|
|
15229
15390
|
};
|
|
15230
15391
|
//#endregion
|
|
15231
15392
|
//#region src/notification-center/text-catalog.ts
|
|
@@ -15479,12 +15640,12 @@ var NC_RULE_AI_WORDS_FALLBACK = 24;
|
|
|
15479
15640
|
*
|
|
15480
15641
|
* | kind (declared `bodyMaxLen`) | words |
|
|
15481
15642
|
* | --- | --- |
|
|
15482
|
-
* |
|
|
15643
|
+
* | (legacy 500-char push) | 12 |
|
|
15483
15644
|
* | pushover (1024) | 24 |
|
|
15484
15645
|
* | discord (2000) | 36 |
|
|
15485
15646
|
* | home-assistant (3500) | 46 |
|
|
15486
15647
|
* | ntfy · gotify · telegram · whatsapp (4096) | 48 |
|
|
15487
|
-
* | webhook (1 000 000) | 120 |
|
|
15648
|
+
* | webhook · zentik (1 000 000) | 120 |
|
|
15488
15649
|
*
|
|
15489
15650
|
* Which is the operator's sentence, arrived at from a declared number: a push
|
|
15490
15651
|
* notification is truncated by the screen and wants few words; a webhook or a
|
|
@@ -29844,7 +30005,7 @@ function buildRuleAiSystemPrompt(shape, language, maxWords, grouped = false) {
|
|
|
29844
30005
|
].join("");
|
|
29845
30006
|
}
|
|
29846
30007
|
/** `HH:MM` in the host timezone. */
|
|
29847
|
-
function clockOf$
|
|
30008
|
+
function clockOf$5(atMs) {
|
|
29848
30009
|
const d = new Date(atMs);
|
|
29849
30010
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
29850
30011
|
}
|
|
@@ -29882,7 +30043,7 @@ function buildRuleAiPrompt(input) {
|
|
|
29882
30043
|
if (shape === "detection-track") {
|
|
29883
30044
|
lines.push(`Camera: ${camera}.${located} Subject: ${subject}. The stills, in order:`);
|
|
29884
30045
|
for (const [i, frame] of input.frames.entries()) {
|
|
29885
|
-
const when = frame.atMs !== void 0 ? ` · ${clockOf$
|
|
30046
|
+
const when = frame.atMs !== void 0 ? ` · ${clockOf$5(frame.atMs)}` : "";
|
|
29886
30047
|
lines.push(`${String(i + 1)}.${when}`);
|
|
29887
30048
|
}
|
|
29888
30049
|
return lines.join("\n");
|
|
@@ -31653,7 +31814,7 @@ var DEFAULT_PROMPT_EN = "What happened during this period? Summarise it for a ho
|
|
|
31653
31814
|
*/
|
|
31654
31815
|
var SummaryAnswerSchema = require_clip_model_registry.object({ summary: require_clip_model_registry.string() });
|
|
31655
31816
|
/** `HH:MM` in the host timezone — the two numbers a frame label needs. */
|
|
31656
|
-
function clockOf$
|
|
31817
|
+
function clockOf$4(atMs) {
|
|
31657
31818
|
const d = new Date(atMs);
|
|
31658
31819
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
31659
31820
|
}
|
|
@@ -31676,7 +31837,7 @@ function buildSummaryAiPrompt(input, frames) {
|
|
|
31676
31837
|
const roster = frames.map((f, i) => {
|
|
31677
31838
|
const location = plainLabel(f.location ?? "");
|
|
31678
31839
|
const where = location.length > 0 ? ` (${location})` : "";
|
|
31679
|
-
return `${String(i + 1)}. ${plainLabel(f.deviceName)}${where} · ${clockOf$
|
|
31840
|
+
return `${String(i + 1)}. ${plainLabel(f.deviceName)}${where} · ${clockOf$4(f.atMs)} · ${plainVocabulary(f.className) || "subject"}`;
|
|
31680
31841
|
}).join("\n");
|
|
31681
31842
|
const described = /* @__PURE__ */ new Map();
|
|
31682
31843
|
for (const f of frames) {
|
|
@@ -32279,7 +32440,7 @@ var NO_DETECTIONS$1 = {
|
|
|
32279
32440
|
* has to check is a key somebody can get wrong. */
|
|
32280
32441
|
var BODY_SEPARATOR$1 = " · ";
|
|
32281
32442
|
/** `HH:MM` in the host timezone. */
|
|
32282
|
-
function clockOf$
|
|
32443
|
+
function clockOf$3(atMs) {
|
|
32283
32444
|
const d = new Date(atMs);
|
|
32284
32445
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
32285
32446
|
}
|
|
@@ -32293,8 +32454,8 @@ function clockOf$4(atMs) {
|
|
|
32293
32454
|
function summaryTemplateVars(input) {
|
|
32294
32455
|
return {
|
|
32295
32456
|
rule: input.ruleName,
|
|
32296
|
-
from: clockOf$
|
|
32297
|
-
to: clockOf$
|
|
32457
|
+
from: clockOf$3(input.window.startMs),
|
|
32458
|
+
to: clockOf$3(input.window.endMs),
|
|
32298
32459
|
time: ncClockText(input.texts.language, input.generatedAt),
|
|
32299
32460
|
events: String(input.tileCount),
|
|
32300
32461
|
cameras: String(input.cameraCount),
|
|
@@ -32389,11 +32550,11 @@ function summaryRecordId(ruleId, windowEndMs) {
|
|
|
32389
32550
|
/** The caption burned onto the mosaic's title band, with the same `{{var}}`
|
|
32390
32551
|
* vocabulary the title and body use. `''` is a real answer ("no caption"). */
|
|
32391
32552
|
function summaryCaptionText(input) {
|
|
32392
|
-
if (input.captionText === void 0) return `${input.ruleName} — ${clockOf$
|
|
32553
|
+
if (input.captionText === void 0) return `${input.ruleName} — ${clockOf$3(input.window.startMs)}→${clockOf$3(input.window.endMs)}`;
|
|
32393
32554
|
return renderTemplate(input.captionText, {
|
|
32394
32555
|
rule: input.ruleName,
|
|
32395
|
-
from: clockOf$
|
|
32396
|
-
to: clockOf$
|
|
32556
|
+
from: clockOf$3(input.window.startMs),
|
|
32557
|
+
to: clockOf$3(input.window.endMs)
|
|
32397
32558
|
}) ?? "";
|
|
32398
32559
|
}
|
|
32399
32560
|
/**
|
|
@@ -33325,7 +33486,7 @@ var NcSummaryProducer = class {
|
|
|
33325
33486
|
purpose: "summary",
|
|
33326
33487
|
tiles: tiles.map(({ candidate, jpeg }) => ({
|
|
33327
33488
|
jpeg,
|
|
33328
|
-
...plan.tileLabels ? { label: `${names.get(candidate.deviceId) ?? candidate.deviceId} · ${clockOf$
|
|
33489
|
+
...plan.tileLabels ? { label: `${names.get(candidate.deviceId) ?? candidate.deviceId} · ${clockOf$2(candidate.firstSeen)} · ${candidate.className}` } : {},
|
|
33329
33490
|
deviceId: candidate.deviceId,
|
|
33330
33491
|
sourceId: candidate.trackId
|
|
33331
33492
|
})),
|
|
@@ -33548,7 +33709,7 @@ var NcSummaryProducer = class {
|
|
|
33548
33709
|
}
|
|
33549
33710
|
};
|
|
33550
33711
|
/** `HH:MM` in the host timezone — the two numbers a tile label needs. */
|
|
33551
|
-
function clockOf$
|
|
33712
|
+
function clockOf$2(atMs) {
|
|
33552
33713
|
const d = new Date(atMs);
|
|
33553
33714
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
33554
33715
|
}
|
|
@@ -34049,8 +34210,6 @@ var NcCalendarEventsStore = class {
|
|
|
34049
34210
|
};
|
|
34050
34211
|
//#endregion
|
|
34051
34212
|
//#region src/notification-center/calendar-events/calendar-events-body.ts
|
|
34052
|
-
/** Visual weight on the tomorrow header — Telegram/Zentik plain text. */
|
|
34053
|
-
var CALENDAR_EVENTS_TOMORROW_MARK = "★";
|
|
34054
34213
|
/** Separates the tomorrow block from the rest of the horizon. */
|
|
34055
34214
|
var CALENDAR_EVENTS_DAY_DELIMITER = "────────";
|
|
34056
34215
|
var MAX_EVENT_LINES = 40;
|
|
@@ -34071,26 +34230,50 @@ function addLocalDays(atMs, days) {
|
|
|
34071
34230
|
d.setDate(d.getDate() + days);
|
|
34072
34231
|
return d.getTime();
|
|
34073
34232
|
}
|
|
34074
|
-
function
|
|
34075
|
-
|
|
34076
|
-
const label = new Date(dayStartMs).toLocaleDateString(void 0, {
|
|
34233
|
+
function dayDateLabel(texts, dayStartMs) {
|
|
34234
|
+
return new Intl.DateTimeFormat(texts.language, {
|
|
34077
34235
|
weekday: "short",
|
|
34078
34236
|
month: "short",
|
|
34079
34237
|
day: "numeric"
|
|
34080
|
-
});
|
|
34081
|
-
if (key === todayKey) return `Today — ${label}`;
|
|
34082
|
-
if (key === tomorrowKey) return `${CALENDAR_EVENTS_TOMORROW_MARK} Tomorrow — ${label}`;
|
|
34083
|
-
return label;
|
|
34238
|
+
}).format(new Date(dayStartMs));
|
|
34084
34239
|
}
|
|
34085
|
-
|
|
34086
|
-
|
|
34087
|
-
const
|
|
34088
|
-
return
|
|
34240
|
+
function dayHeadingFor(texts, dayStartMs, todayKey, tomorrowKey) {
|
|
34241
|
+
const key = localDayKey(dayStartMs);
|
|
34242
|
+
const date = dayDateLabel(texts, dayStartMs);
|
|
34243
|
+
if (key === todayKey) return texts.text({
|
|
34244
|
+
key: "calendar-events.day.today",
|
|
34245
|
+
vars: { date }
|
|
34246
|
+
});
|
|
34247
|
+
if (key === tomorrowKey) return texts.text({
|
|
34248
|
+
key: "calendar-events.day.tomorrow",
|
|
34249
|
+
vars: { date }
|
|
34250
|
+
});
|
|
34251
|
+
return texts.text({
|
|
34252
|
+
key: "calendar-events.day.other",
|
|
34253
|
+
vars: { date }
|
|
34254
|
+
});
|
|
34089
34255
|
}
|
|
34090
|
-
function formatEventLine(event) {
|
|
34091
|
-
const
|
|
34092
|
-
|
|
34093
|
-
|
|
34256
|
+
function formatEventLine(texts, event) {
|
|
34257
|
+
const where = event.location !== void 0 && event.location.length > 0 ? texts.text({
|
|
34258
|
+
key: "calendar-events.where",
|
|
34259
|
+
vars: { location: event.location }
|
|
34260
|
+
}) : "";
|
|
34261
|
+
if (event.allDay === true) return texts.text({
|
|
34262
|
+
key: "calendar-events.line.allDay",
|
|
34263
|
+
vars: {
|
|
34264
|
+
title: event.title,
|
|
34265
|
+
where
|
|
34266
|
+
}
|
|
34267
|
+
});
|
|
34268
|
+
const when = `${ncClockText(texts.language, event.startMs)}–${ncClockText(texts.language, event.endMs)}`;
|
|
34269
|
+
return texts.text({
|
|
34270
|
+
key: "calendar-events.line.timed",
|
|
34271
|
+
vars: {
|
|
34272
|
+
when,
|
|
34273
|
+
title: event.title,
|
|
34274
|
+
where
|
|
34275
|
+
}
|
|
34276
|
+
});
|
|
34094
34277
|
}
|
|
34095
34278
|
function groupByLocalDay(events) {
|
|
34096
34279
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -34114,8 +34297,11 @@ function groupByLocalDay(events) {
|
|
|
34114
34297
|
*
|
|
34115
34298
|
* @param nowMs — fire time; tomorrow is the local calendar day after this.
|
|
34116
34299
|
*/
|
|
34117
|
-
function formatCalendarEventsBody(events, nowMs, horizonDays) {
|
|
34118
|
-
if (events.length === 0) return
|
|
34300
|
+
function formatCalendarEventsBody(texts, events, nowMs, horizonDays) {
|
|
34301
|
+
if (events.length === 0) return texts.text({
|
|
34302
|
+
key: "calendar-events.empty",
|
|
34303
|
+
vars: { horizonDays: String(horizonDays) }
|
|
34304
|
+
});
|
|
34119
34305
|
const todayKey = localDayKey(nowMs);
|
|
34120
34306
|
const tomorrowKey = localDayKey(addLocalDays(startOfLocalDay(nowMs), 1));
|
|
34121
34307
|
const buckets = groupByLocalDay(events);
|
|
@@ -34127,17 +34313,20 @@ function formatCalendarEventsBody(events, nowMs, horizonDays) {
|
|
|
34127
34313
|
let emitted = 0;
|
|
34128
34314
|
const pushBucket = (bucket) => {
|
|
34129
34315
|
if (emitted >= MAX_EVENT_LINES) return;
|
|
34130
|
-
lines.push(dayHeadingFor(bucket.dayStartMs, todayKey, tomorrowKey));
|
|
34316
|
+
lines.push(dayHeadingFor(texts, bucket.dayStartMs, todayKey, tomorrowKey));
|
|
34131
34317
|
for (const ev of bucket.events) {
|
|
34132
34318
|
if (emitted >= MAX_EVENT_LINES) break;
|
|
34133
|
-
lines.push(formatEventLine(ev));
|
|
34319
|
+
lines.push(formatEventLine(texts, ev));
|
|
34134
34320
|
emitted += 1;
|
|
34135
34321
|
}
|
|
34136
34322
|
};
|
|
34137
34323
|
for (const bucket of lead) pushBucket(bucket);
|
|
34138
34324
|
if (lead.length > 0 && later.length > 0) lines.push(CALENDAR_EVENTS_DAY_DELIMITER);
|
|
34139
34325
|
for (const bucket of later) pushBucket(bucket);
|
|
34140
|
-
if (events.length > MAX_EVENT_LINES) lines.push(
|
|
34326
|
+
if (events.length > MAX_EVENT_LINES) lines.push(texts.text({
|
|
34327
|
+
key: "calendar-events.more",
|
|
34328
|
+
vars: { count: String(events.length - MAX_EVENT_LINES) }
|
|
34329
|
+
}));
|
|
34141
34330
|
return lines.join("\n");
|
|
34142
34331
|
}
|
|
34143
34332
|
//#endregion
|
|
@@ -34151,7 +34340,7 @@ function clockOf$1(atMs) {
|
|
|
34151
34340
|
return `${`${String(d.getFullYear())}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`} ${`${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`}`;
|
|
34152
34341
|
}
|
|
34153
34342
|
function derivedBody$1(input) {
|
|
34154
|
-
return formatCalendarEventsBody(input.events, input.eventWindow.startMs, input.horizonDays);
|
|
34343
|
+
return formatCalendarEventsBody(input.texts, input.events, input.eventWindow.startMs, input.horizonDays);
|
|
34155
34344
|
}
|
|
34156
34345
|
function templateVars(input) {
|
|
34157
34346
|
return {
|
|
@@ -34177,7 +34366,11 @@ function paramsFor$1(target) {
|
|
|
34177
34366
|
function buildCalendarEventsOutboxInputs(input) {
|
|
34178
34367
|
const vars = templateVars(input);
|
|
34179
34368
|
const optedOut = new Set(input.disabledTargetIds ?? []);
|
|
34180
|
-
const title =
|
|
34369
|
+
const title = input.texts.text({
|
|
34370
|
+
key: "calendar-events.title",
|
|
34371
|
+
template: input.template?.title,
|
|
34372
|
+
vars
|
|
34373
|
+
});
|
|
34181
34374
|
const body = renderTemplate(input.template?.body, vars) ?? derivedBody$1(input);
|
|
34182
34375
|
const recordId = calendarEventsRecordId(input.ruleId, input.scheduleWindow.endMs);
|
|
34183
34376
|
return input.targets.filter((t) => !optedOut.has(t.targetId)).map((target) => {
|
|
@@ -34326,6 +34519,7 @@ var NcCalendarEventsProducer = class {
|
|
|
34326
34519
|
}
|
|
34327
34520
|
async enqueue(rule, scheduleWindow, eventWindow, calendarIds, events) {
|
|
34328
34521
|
const inputs = buildCalendarEventsOutboxInputs({
|
|
34522
|
+
texts: this.deps.texts,
|
|
34329
34523
|
ruleId: rule.id,
|
|
34330
34524
|
ruleName: rule.name,
|
|
34331
34525
|
scheduleWindow,
|
|
@@ -34340,7 +34534,7 @@ var NcCalendarEventsProducer = class {
|
|
|
34340
34534
|
priority: rule.priority,
|
|
34341
34535
|
...rule.template !== void 0 ? { template: rule.template } : {}
|
|
34342
34536
|
});
|
|
34343
|
-
|
|
34537
|
+
await this.deps.outbox.enqueue(inputs);
|
|
34344
34538
|
this.deps.logger.info("calendar-events delivered", { meta: {
|
|
34345
34539
|
ruleId: rule.id,
|
|
34346
34540
|
events: events.length,
|
|
@@ -35699,16 +35893,25 @@ function stateItem(row, lookup) {
|
|
|
35699
35893
|
/**
|
|
35700
35894
|
* THE RECONCILE (D647 review I2, D8): a ledger row is a memory, never the
|
|
35701
35895
|
* truth. A device item is listed only while the device EXISTS in the directory
|
|
35702
|
-
*
|
|
35703
|
-
*
|
|
35704
|
-
*
|
|
35705
|
-
*
|
|
35896
|
+
* — and an "offline" one only while the device manager does not call it
|
|
35897
|
+
* online. A node item only while the node is known to a complete node
|
|
35898
|
+
* directory (a forgotten node is gone), and an "offline" one only while the
|
|
35899
|
+
* directory does not call it online.
|
|
35900
|
+
*
|
|
35901
|
+
* Accessories are silenced only on the liveness / switched-off section
|
|
35902
|
+
* (`device-state`). Battery, consumable and tank alerts ride the SAME
|
|
35903
|
+
* reconcile, and a vacuum that is a child of its Dreame container
|
|
35904
|
+
* (`parentDeviceId` set) still owns those alerts — filtering every accessory
|
|
35905
|
+
* here dropped every tank line of the 2026-10-05 evening summary for
|
|
35906
|
+
* deviceId 4385 while the episode was open and the instant path had already
|
|
35907
|
+
* held `device-tank-low` for that rule.
|
|
35706
35908
|
*/
|
|
35707
35909
|
function stillTrue(item, ports) {
|
|
35708
35910
|
const deviceId = item.subject.deviceId;
|
|
35709
35911
|
if (deviceId !== void 0) {
|
|
35710
35912
|
const identity = ports.lookup(deviceId);
|
|
35711
|
-
if (identity === void 0
|
|
35913
|
+
if (identity === void 0) return false;
|
|
35914
|
+
if (item.section === "device-state" && isAccessoryIdentity(identity)) return false;
|
|
35712
35915
|
if (item.subject.kind === "device-offline" && identity.online === true) return false;
|
|
35713
35916
|
}
|
|
35714
35917
|
const nodeId = item.subject.nodeId;
|
|
@@ -38817,6 +39020,7 @@ var NotificationCenter = class {
|
|
|
38817
39020
|
store: this.calendarEventsRules,
|
|
38818
39021
|
outbox: this.outbox,
|
|
38819
39022
|
calendars: calendarsPort,
|
|
39023
|
+
texts: this.texts,
|
|
38820
39024
|
logger: this.logger.child("calendar-events"),
|
|
38821
39025
|
now: this.now
|
|
38822
39026
|
});
|
|
@@ -40987,8 +41191,14 @@ var NotificationCenter = class {
|
|
|
40987
41191
|
}
|
|
40988
41192
|
if (kind !== "alarm-armed" && kind !== "alarm-arming") return null;
|
|
40989
41193
|
if (mode === void 0) return null;
|
|
40990
|
-
const coverage = this.coverageFor(panel).find((c) => c.mode === mode)
|
|
40991
|
-
|
|
41194
|
+
const coverage = this.coverageFor(panel).find((c) => c.mode === mode) ?? {
|
|
41195
|
+
mode,
|
|
41196
|
+
ruleCount: 0,
|
|
41197
|
+
allDevices: false,
|
|
41198
|
+
allDevicesExcept: [],
|
|
41199
|
+
deviceIds: [],
|
|
41200
|
+
skippedDevices: []
|
|
41201
|
+
};
|
|
40992
41202
|
const name = this.deviceNameFromDirectory();
|
|
40993
41203
|
const bypassed = panel.excludedDevices();
|
|
40994
41204
|
const text = kind === "alarm-arming" ? buildArmingAnnouncement(coverage, transition.seconds ?? 0, name, bypassed) : buildArmAnnouncement(coverage, name, bypassed);
|
|
@@ -57660,36 +57870,68 @@ var DEFAULT_STATE_ANALYZER_CONFIG = {
|
|
|
57660
57870
|
stillWindowMs: DEFAULT_STILL_WINDOW_MS,
|
|
57661
57871
|
stillSpanFraction: DEFAULT_STILL_SPAN_FRACTION
|
|
57662
57872
|
};
|
|
57873
|
+
function shortSideOf(bbox) {
|
|
57874
|
+
return Math.min(bbox.w, bbox.h);
|
|
57875
|
+
}
|
|
57663
57876
|
var StateAnalyzer = class {
|
|
57664
57877
|
config;
|
|
57665
57878
|
states = /* @__PURE__ */ new Map();
|
|
57879
|
+
parked = /* @__PURE__ */ new Map();
|
|
57666
57880
|
constructor(config = {}) {
|
|
57667
57881
|
this.config = {
|
|
57668
57882
|
...DEFAULT_STATE_ANALYZER_CONFIG,
|
|
57669
57883
|
...config
|
|
57670
57884
|
};
|
|
57671
57885
|
}
|
|
57672
|
-
/**
|
|
57886
|
+
/**
|
|
57887
|
+
* D683: a fused continuation keeps its movement state under the survivor id.
|
|
57888
|
+
*
|
|
57889
|
+
* The absorbed piece is the live continuation, but a still subject that L1
|
|
57890
|
+
* split must not lose the dwell the survivor already paid — `rename` MERGES
|
|
57891
|
+
* the stillness clocks when the geometry across the gap is still still, and
|
|
57892
|
+
* only takes the live piece alone when the subject actually moved.
|
|
57893
|
+
*/
|
|
57673
57894
|
rename(from, to) {
|
|
57674
|
-
|
|
57895
|
+
const live = this.states.get(from) ?? this.parked.get(from)?.state;
|
|
57896
|
+
if (live === void 0) return;
|
|
57897
|
+
this.states.delete(from);
|
|
57898
|
+
this.parked.delete(from);
|
|
57899
|
+
const survivor = this.states.get(to) ?? this.parked.get(to)?.state;
|
|
57900
|
+
this.parked.delete(to);
|
|
57901
|
+
if (survivor === void 0) {
|
|
57902
|
+
this.states.set(to, live);
|
|
57903
|
+
return;
|
|
57904
|
+
}
|
|
57905
|
+
this.states.set(to, this.mergeContinuation(survivor, live));
|
|
57675
57906
|
}
|
|
57676
57907
|
analyze(tracks, timestamp) {
|
|
57908
|
+
this.pruneParked(timestamp);
|
|
57677
57909
|
const currentIds = new Set(tracks.map((t) => t.trackId));
|
|
57678
57910
|
const results = [];
|
|
57679
57911
|
for (const track of tracks) {
|
|
57680
57912
|
let ts = this.states.get(track.trackId);
|
|
57913
|
+
if (ts === void 0) {
|
|
57914
|
+
const parked = this.parked.get(track.trackId);
|
|
57915
|
+
if (parked !== void 0) {
|
|
57916
|
+
this.parked.delete(track.trackId);
|
|
57917
|
+
ts = parked.state;
|
|
57918
|
+
this.states.set(track.trackId, ts);
|
|
57919
|
+
}
|
|
57920
|
+
}
|
|
57681
57921
|
const center = bboxCentroid({
|
|
57682
57922
|
x: track.bbox.x,
|
|
57683
57923
|
y: track.bbox.y,
|
|
57684
57924
|
w: track.bbox.w,
|
|
57685
57925
|
h: track.bbox.h
|
|
57686
57926
|
});
|
|
57927
|
+
const shortSide = shortSideOf(track.bbox);
|
|
57687
57928
|
if (!ts) {
|
|
57688
57929
|
ts = {
|
|
57689
57930
|
enteredAt: timestamp,
|
|
57690
57931
|
stationarySince: void 0,
|
|
57691
57932
|
totalDistancePx: 0,
|
|
57692
57933
|
lastPosition: center,
|
|
57934
|
+
lastShortSide: shortSide,
|
|
57693
57935
|
frameCount: 1,
|
|
57694
57936
|
recent: [{
|
|
57695
57937
|
x: center.x,
|
|
@@ -57704,14 +57946,14 @@ var StateAnalyzer = class {
|
|
|
57704
57946
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
57705
57947
|
ts.totalDistancePx += dist;
|
|
57706
57948
|
ts.lastPosition = center;
|
|
57949
|
+
ts.lastShortSide = shortSide;
|
|
57707
57950
|
ts.frameCount++;
|
|
57708
57951
|
ts.recent.push({
|
|
57709
57952
|
x: center.x,
|
|
57710
57953
|
y: center.y,
|
|
57711
57954
|
at: timestamp
|
|
57712
57955
|
});
|
|
57713
|
-
|
|
57714
|
-
while (ts.recent.length > 2 && ts.recent[1].at < cutoff) ts.recent.shift();
|
|
57956
|
+
this.trimRecent(ts, timestamp);
|
|
57715
57957
|
}
|
|
57716
57958
|
const velocity = track.velocity ? Math.sqrt(track.velocity.dx ** 2 + track.velocity.dy ** 2) : 0;
|
|
57717
57959
|
const dwellTimeMs = timestamp - ts.enteredAt;
|
|
@@ -57747,10 +57989,62 @@ var StateAnalyzer = class {
|
|
|
57747
57989
|
dwellTimeMs: timestamp - ts.enteredAt
|
|
57748
57990
|
});
|
|
57749
57991
|
this.states.delete(trackId);
|
|
57992
|
+
this.parked.set(trackId, {
|
|
57993
|
+
state: ts,
|
|
57994
|
+
parkedAt: timestamp
|
|
57995
|
+
});
|
|
57750
57996
|
}
|
|
57751
57997
|
return results;
|
|
57752
57998
|
}
|
|
57753
57999
|
/**
|
|
58000
|
+
* Fold a live continuation into its ended survivor.
|
|
58001
|
+
*
|
|
58002
|
+
* Stillness is what the parked-object ledger votes on (`idle` only). A gap
|
|
58003
|
+
* past L1's coast budget that L2 later heals must not restart that clock
|
|
58004
|
+
* when the centroid never left the still span. A continuation that DID move
|
|
58005
|
+
* drops the survivor's `stationarySince` so a walk after a pause is still
|
|
58006
|
+
* `moving`.
|
|
58007
|
+
*/
|
|
58008
|
+
mergeContinuation(survivor, live) {
|
|
58009
|
+
const gapTravel = Math.hypot(live.lastPosition.x - survivor.lastPosition.x, live.lastPosition.y - survivor.lastPosition.y);
|
|
58010
|
+
const subject = Math.max(survivor.lastShortSide, live.lastShortSide);
|
|
58011
|
+
const stillAcrossGap = survivor.stationarySince !== void 0 && subject > 0 && gapTravel <= subject * this.config.stillSpanFraction;
|
|
58012
|
+
const lastAt = live.recent[live.recent.length - 1]?.at;
|
|
58013
|
+
if (stillAcrossGap && survivor.stationarySince !== void 0 && lastAt !== void 0) {
|
|
58014
|
+
const anchorAt = lastAt - this.config.stillWindowMs;
|
|
58015
|
+
const recent = [{
|
|
58016
|
+
x: live.lastPosition.x,
|
|
58017
|
+
y: live.lastPosition.y,
|
|
58018
|
+
at: anchorAt
|
|
58019
|
+
}, ...live.recent.filter((s) => s.at > anchorAt)];
|
|
58020
|
+
return {
|
|
58021
|
+
enteredAt: Math.min(survivor.enteredAt, live.enteredAt),
|
|
58022
|
+
stationarySince: live.stationarySince !== void 0 ? Math.min(survivor.stationarySince, live.stationarySince) : survivor.stationarySince,
|
|
58023
|
+
totalDistancePx: survivor.totalDistancePx + live.totalDistancePx,
|
|
58024
|
+
lastPosition: live.lastPosition,
|
|
58025
|
+
lastShortSide: live.lastShortSide,
|
|
58026
|
+
frameCount: Math.max(survivor.frameCount + live.frameCount, this.config.enteringFrames + 1),
|
|
58027
|
+
recent
|
|
58028
|
+
};
|
|
58029
|
+
}
|
|
58030
|
+
return {
|
|
58031
|
+
enteredAt: live.enteredAt,
|
|
58032
|
+
stationarySince: live.stationarySince,
|
|
58033
|
+
totalDistancePx: survivor.totalDistancePx + live.totalDistancePx,
|
|
58034
|
+
lastPosition: live.lastPosition,
|
|
58035
|
+
lastShortSide: live.lastShortSide,
|
|
58036
|
+
frameCount: live.frameCount,
|
|
58037
|
+
recent: [...live.recent]
|
|
58038
|
+
};
|
|
58039
|
+
}
|
|
58040
|
+
trimRecent(ts, timestamp) {
|
|
58041
|
+
const cutoff = timestamp - this.config.stillWindowMs;
|
|
58042
|
+
while (ts.recent.length > 2 && ts.recent[1].at < cutoff) ts.recent.shift();
|
|
58043
|
+
}
|
|
58044
|
+
pruneParked(timestamp) {
|
|
58045
|
+
for (const [id, parked] of this.parked) if (timestamp - parked.parkedAt > LINK_WINDOW_MS) this.parked.delete(id);
|
|
58046
|
+
}
|
|
58047
|
+
/**
|
|
57754
58048
|
* The SPEED half of the settled test.
|
|
57755
58049
|
*
|
|
57756
58050
|
* Uncalibrated (`stillSpeedFracPerSec` undefined) this is the original line
|
|
@@ -57812,6 +58106,7 @@ var StateAnalyzer = class {
|
|
|
57812
58106
|
}
|
|
57813
58107
|
reset() {
|
|
57814
58108
|
this.states.clear();
|
|
58109
|
+
this.parked.clear();
|
|
57815
58110
|
}
|
|
57816
58111
|
};
|
|
57817
58112
|
//#endregion
|
|
@@ -87001,23 +87296,19 @@ function sweepOne(deps, deviceId) {
|
|
|
87001
87296
|
}
|
|
87002
87297
|
//#endregion
|
|
87003
87298
|
//#region src/pipeline-analytics/pipeline/stationary/stationary-zones.ts
|
|
87299
|
+
/** Shared with every call — ZoneEngine is stateless for annotateDetection. */
|
|
87300
|
+
var ENGINE = new ZoneEngine();
|
|
87004
87301
|
/**
|
|
87005
|
-
* Zone ids whose 0–1
|
|
87006
|
-
*
|
|
87302
|
+
* Zone ids whose bbox overlap exceeds `minOverlap` (0–1 fraction of the
|
|
87303
|
+
* detection's own area) — identical to {@link ZoneEngine.annotateDetection}.
|
|
87304
|
+
* Empty when the entry has no frame dims or no zone meets the bar.
|
|
87305
|
+
*
|
|
87306
|
+
* `minOverlap` is required: callers pass `TrackingSettings.zoneMembershipMinOverlap`
|
|
87307
|
+
* so parked and live never disagree on the threshold.
|
|
87007
87308
|
*/
|
|
87008
|
-
function computeStationaryEntryZones(entry, zones) {
|
|
87309
|
+
function computeStationaryEntryZones(entry, zones, minOverlap) {
|
|
87009
87310
|
if (entry.frameWidth <= 0 || entry.frameHeight <= 0 || zones.length === 0) return [];
|
|
87010
|
-
|
|
87011
|
-
const point = {
|
|
87012
|
-
x: centroidPx.x / entry.frameWidth,
|
|
87013
|
-
y: centroidPx.y / entry.frameHeight
|
|
87014
|
-
};
|
|
87015
|
-
const matched = [];
|
|
87016
|
-
for (const zone of zones) {
|
|
87017
|
-
if (zone.polygon.length < 3) continue;
|
|
87018
|
-
if (pointInPolygon$1(point, zone.polygon)) matched.push(zone.id);
|
|
87019
|
-
}
|
|
87020
|
-
return matched;
|
|
87311
|
+
return ENGINE.annotateDetection(entry.bbox, zones, entry.frameWidth, entry.frameHeight, void 0, void 0, void 0, minOverlap).map((m) => m.zoneId);
|
|
87021
87312
|
}
|
|
87022
87313
|
//#endregion
|
|
87023
87314
|
//#region src/pipeline-analytics/pipeline/package-drop-detector.ts
|
|
@@ -87117,15 +87408,17 @@ var PackageDropDetector = class {
|
|
|
87117
87408
|
}
|
|
87118
87409
|
const ruleZoneIds = new Set(rules.flatMap((r) => r.zoneIds));
|
|
87119
87410
|
const zones = await this.deps.resolveZones(entry.deviceId);
|
|
87120
|
-
const
|
|
87411
|
+
const minOverlap = await this.deps.resolveZoneMembershipMinOverlap(entry.deviceId);
|
|
87412
|
+
const entryZones = computeStationaryEntryZones(entry, zones, minOverlap);
|
|
87121
87413
|
const hitZones = entryZones.filter((z) => ruleZoneIds.has(z));
|
|
87122
87414
|
if (hitZones.length === 0) {
|
|
87123
|
-
this.deps.logger.info("package gate 2/5 zone —
|
|
87415
|
+
this.deps.logger.info("package gate 2/5 zone — no package zone meets membership overlap", {
|
|
87124
87416
|
tags,
|
|
87125
87417
|
meta: {
|
|
87126
87418
|
entryId: entry.id,
|
|
87127
87419
|
bbox: entry.bbox,
|
|
87128
87420
|
entryZones,
|
|
87421
|
+
minOverlap,
|
|
87129
87422
|
ruleZoneIds: [...ruleZoneIds],
|
|
87130
87423
|
zonesOnDevice: zones.length
|
|
87131
87424
|
}
|
|
@@ -100897,7 +101190,7 @@ function buildDetectionSettingsSections() {
|
|
|
100897
101190
|
type: "number",
|
|
100898
101191
|
key: "zoneMembershipMinOverlap",
|
|
100899
101192
|
label: "Zone membership minimum overlap",
|
|
100900
|
-
description: "How much of a detection box must fall inside a zone for the detection to be STAMPED with it — as a fraction of the BOX area, not of the zone. 0 stamps on any touch, which is what let a plant brushing a zone by 6% fire a \"person on the doorstep\" rule. Only a stamped zone can match a zone-scoped notification rule. Read the \"zone membership stamped on event\" log line to choose the value: it prints the achieved percentage per zone.",
|
|
101193
|
+
description: "How much of a detection box must fall inside a zone for the detection to be STAMPED with it — as a fraction of the BOX area, not of the zone. Same bar for live tracks and parked (stationary) occupancy. 0 stamps on any touch, which is what let a plant brushing a zone by 6% fire a \"person on the doorstep\" rule. Only a stamped zone can match a zone-scoped notification rule. Read the \"zone membership stamped on event\" log line to choose the value: it prints the achieved percentage per zone.",
|
|
100901
101194
|
min: 0,
|
|
100902
101195
|
max: 1,
|
|
100903
101196
|
step: .05,
|
|
@@ -104118,14 +104411,21 @@ var ZoneAnalyticsProvider = class {
|
|
|
104118
104411
|
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
104119
104412
|
});
|
|
104120
104413
|
}
|
|
104414
|
+
const minOverlap = await this.resolveMinOverlap(deviceId);
|
|
104121
104415
|
const ts = mostRecentStationaryConfirmedAt(entries) || Date.now();
|
|
104122
104416
|
return buildStationarySnapshot({
|
|
104123
104417
|
deviceId,
|
|
104124
104418
|
entries,
|
|
104125
104419
|
zones,
|
|
104126
|
-
timestamp: ts
|
|
104420
|
+
timestamp: ts,
|
|
104421
|
+
minOverlap
|
|
104127
104422
|
});
|
|
104128
104423
|
}
|
|
104424
|
+
/** Tracking membership bar — schema default when the host did not wire it. */
|
|
104425
|
+
async resolveMinOverlap(deviceId) {
|
|
104426
|
+
if (this.ctx.resolveZoneMembershipMinOverlap === void 0) return TRACKING_DEFAULTS.zoneMembershipMinOverlap;
|
|
104427
|
+
return this.ctx.resolveZoneMembershipMinOverlap(deviceId);
|
|
104428
|
+
}
|
|
104129
104429
|
/**
|
|
104130
104430
|
* Baseline sampler tick: for every device with parked objects, append a
|
|
104131
104431
|
* hydrated sample to the history ring at the CURRENT time — but only when a
|
|
@@ -104143,11 +104443,13 @@ var ZoneAnalyticsProvider = class {
|
|
|
104143
104443
|
try {
|
|
104144
104444
|
zones = await this.ctx.resolveZones?.(deviceId) ?? [];
|
|
104145
104445
|
} catch {}
|
|
104446
|
+
const minOverlap = await this.resolveMinOverlap(deviceId);
|
|
104146
104447
|
const snapshot = buildStationarySnapshot({
|
|
104147
104448
|
deviceId,
|
|
104148
104449
|
entries,
|
|
104149
104450
|
zones,
|
|
104150
|
-
timestamp: now
|
|
104451
|
+
timestamp: now,
|
|
104452
|
+
minOverlap
|
|
104151
104453
|
});
|
|
104152
104454
|
if (snapshot) {
|
|
104153
104455
|
this.appendHistory(deviceId, snapshot);
|
|
@@ -104304,18 +104606,18 @@ function mostRecentStationaryConfirmedAt(entries) {
|
|
|
104304
104606
|
/**
|
|
104305
104607
|
* Build an occupancy snapshot from parked-object registry entries alone —
|
|
104306
104608
|
* used when no live inference frame is available (fresh respawn, camera
|
|
104307
|
-
* detached). Each entry is folded into the frame aggregate AND attributed
|
|
104308
|
-
*
|
|
104309
|
-
*
|
|
104310
|
-
*
|
|
104311
|
-
*
|
|
104609
|
+
* detached). Each entry is folded into the frame aggregate AND attributed via
|
|
104610
|
+
* {@link computeStationaryEntryZones} (same `ZoneEngine` + `minOverlap` as
|
|
104611
|
+
* live stamps), so a zone drawn over a parked car reports a count of 1.
|
|
104612
|
+
* Returns `null` for an empty entry list. Reuses {@link computeSnapshot} —
|
|
104613
|
+
* the SAME aggregation the live frame path runs.
|
|
104312
104614
|
*/
|
|
104313
104615
|
function buildStationarySnapshot(input) {
|
|
104314
104616
|
if (input.entries.length === 0) return null;
|
|
104315
104617
|
const tracked = input.entries.map((e) => ({
|
|
104316
104618
|
trackId: `stationary:${e.id}`,
|
|
104317
104619
|
className: e.className,
|
|
104318
|
-
zones: computeStationaryEntryZones(e, input.zones),
|
|
104620
|
+
zones: computeStationaryEntryZones(e, input.zones, input.minOverlap),
|
|
104319
104621
|
standing: true
|
|
104320
104622
|
}));
|
|
104321
104623
|
const first = input.entries[0];
|
|
@@ -107138,6 +107440,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
107138
107440
|
resolveZones: (deviceId) => this.resolveDeviceZones(deviceId),
|
|
107139
107441
|
resolvePackageRules: (deviceId) => this.resolveDevicePackageRules(deviceId),
|
|
107140
107442
|
resolveSettings: (deviceId) => this.resolveDevicePackageDropSettings(deviceId),
|
|
107443
|
+
resolveZoneMembershipMinOverlap: async (deviceId) => (await this.resolveDeviceTrackingSettings(deviceId)).zoneMembershipMinOverlap,
|
|
107141
107444
|
onPersisted: (ev, phase) => this.notificationCenter?.onPackageEventPersisted(ev, phase),
|
|
107142
107445
|
onError: (scope, err) => logger.warn("package-drop detector error", { meta: {
|
|
107143
107446
|
scope,
|
|
@@ -107433,6 +107736,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
107433
107736
|
listStationaryObjects: (deviceId) => this.stationaryRegistry?.listViews(deviceId) ?? [],
|
|
107434
107737
|
listStationaryDeviceIds: () => this.stationaryRegistry?.deviceIds() ?? [],
|
|
107435
107738
|
resolveZones: (deviceId) => this.resolveDeviceZones(deviceId),
|
|
107739
|
+
resolveZoneMembershipMinOverlap: async (deviceId) => (await this.resolveDeviceTrackingSettings(deviceId)).zoneMembershipMinOverlap,
|
|
107436
107740
|
onSnapshot: (deviceId, snapshot, evidence) => this.notificationCenter?.observeOccupancy(deviceId, snapshot, evidence)
|
|
107437
107741
|
});
|
|
107438
107742
|
this.zoneAnalytics = zoneAnalytics;
|
|
@@ -109769,7 +110073,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
109769
110073
|
const stationaryAsTracked = stationaryViews.map((v) => ({
|
|
109770
110074
|
trackId: `stationary:${v.id}`,
|
|
109771
110075
|
className: v.className,
|
|
109772
|
-
zones: computeStationaryEntryZones(v, liveZones),
|
|
110076
|
+
zones: computeStationaryEntryZones(v, liveZones, trk.zoneMembershipMinOverlap),
|
|
109773
110077
|
standing: true
|
|
109774
110078
|
}));
|
|
109775
110079
|
this.zoneAnalytics?.recordFrame({
|