@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as NC_ALARM_DELAY_MAX_SEC, $n as failureContributionCapability, $r as canonicalHash, $t as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, A as EMBEDDING_REQUIREMENTS_FAILED, Ai as unknown, An as assertTimelapseCadences, At as NcRuleInputSchema, B as ListTrackRelationsInputSchema, Bn as deriveRecordingMode, Br as storageOccupancyCapability, Bt as NcStateSummarySectionIdSchema, C as DEFAULT_EVENT_COLOR, Ci as partialRecord, Cn as addonWidgetsSourceCapability, Cr as readDeviceStateFrom, Ct as NcAlarmSensorsSchema, D as DeclaredDevices, Di as templateLiteral, Dn as applyModeDelaysEdits, Dr as resolveClipSearchMinScore, Dt as NcLegacyRuleActionSequenceSchema, E as DETECTION_PIPELINE_CAP_NAME, Ei as string, En as applyBlockingEdit, Er as renameAlarmProfile, Et as NcConditionsSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as consumableLowPercentOf, Fr as ruleKindOf, Ft as NcScheduleSchema, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as embeddingRefusalOf, Gr as systemEventFilterApplies, Gt as NcTemplateVarDescriptorSchema, H as LockStateSchema, Hn as describeSystemEventFamilyProblem, Hr as syncActiveAlarmProfileSettings, Ht as NcTaxonomySchema, I as FIRST_LEVEL_MACRO_CLASSES, In as cosineSimilarity$1, Ir as sceneMonitorCapability, It as NcSharedSequenceSchema, J as MOTION_CLOSE_AFTER_MS, Jn as encodeVectorBase64, Jr as templateCarriesSummary, K as MAX_TRACK_PURITIES, Kr as systemEventSummaryScheduleOf, Kt as OPS_LOG_DEFAULT_LIMIT, L as FULL_IMAGE_BBOX, Ln as createAlarmProfile, Lr as secureActuationFor, Lt as NcSnoozeInputSchema, M as EVENT_DENSITY_BATCH_MAX, Mn as audioModeOf, Mr as ruleBelongsToAlarm$1, Mt as NcRuleSchema, N as EVENT_KIND_BY_CAP, Nn as buildEventKindDescriptor, Nr as ruleDeviceCompatCapQueries, Nt as NcRuleTargetSchema, O as DetectionSourceSchema, Oi as tuple, On as applyNonBlockingEdit, Or as resolveLocationMode, Ot as NcRuleActionSchema, P as EVENT_OWNER_TYPES, Pn as cloneAlarmSettings, Pr as ruleGroupingApplies, Pt as NcRuleUpdateInputSchema, Q as NC_ALARM_DELAY_FIELDS, Qn as faceGalleryCapability, Qr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Qt as RetrainStatusSchema, R as FailureCounters, Rn as deleteAlarmProfile, Rr as sensorRaisedOf, Rt as NcSnoozeSchema, S as CoverStateSchema, Si as object, Sn as activateAlarmProfile, Sr as plateGalleryCapability, St as NcAlarmSensorsInputSchema, T as DETECTION_MACRO_CLASSES, Ti as strictObject, Tn as alarmPanelCapability, Tr as readTimelapseGeneratedAt, Tt as NcConditionDescriptorSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Ur as systemEventDeliveryProblems, Ut as NcTemplatePreviewInputSchema, V as ListTrackRelationsResultSchema, Vn as describeSystemEventDeliveryProblem, Vr as subKindsOf, Vt as NcSystemEventKindSchema, W as MAX_BIRTH_DECISION_RECORDS, Wr as systemEventFamilyProblems, Wt as NcTemplatePreviewSchema, X as MotionTriggeredBySchema, Xn as eventKindLabel, Xr as vectorDimFromBase64, Xt as RECORDING_SOURCE_CAMSTACK_ADDON, Y as MediaFileKindEnum, Yn as ensureAlarmProfiles, Yr as templateVarsFor, Yt as RECORDING_EXPORT_MAX_READ_BYTES, Z as NC_ALARM_DEFAULT_PROFILE_ID, Zn as evictionPolicyOfLocation, Zr as zoneAnalyticsCapability, Zt as RebuildObjectEmbeddingsRefusalSchema, _ as BaseDevice, _i as boolean, _n as TrackPuritySchema, _r as memoizePinnedFfmpeg, _t as NC_TANK_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, ai as CamProfileSchema, an as SCENE_DEFAULT_UNCOVERED_POLICY, ar as isDetectionMacroClass, at as NC_CLIP_PRE_ROLL_DEFAULT_SEC, b as ClearTrackRelationInputSchema, bi as looseObject, bn as TrackSideSchema, bt as NcAlarmConfigSchema, c as resolveClusterModelPin, ci as defineCustomActions, cn as SceneMonitorSchema, cr as isKnownTemplateVar, ct as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, di as nodePin, dn as SetTrackRelationInputSchema, dr as isScheduleActive, dt as NC_GROUPING_DEFAULT_SEC, ei as evaluateSensorEdge, en as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, er as gifDeriveSpeed, et as NC_ALARM_SYSTEM_EVENT_KINDS, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fn as TIMELAPSE_DENSE_FLOOR_SEC, fr as isSystemDelivery, ft as NC_GROUPING_MAX_SEC, g as BIRTH_DECISION_DEFAULT_LIMIT, gi as array, gn as TimelapseRuleSchema, gr as mayWriteToLocation, gt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, h as ArchivedDebugNoteSchema, hi as _enum, hn as TimelapseRulePatchSchema, hr as kebabToCamel, ht as NC_SNOOZE_MAX_MINUTES, ii as BaseAddon, in as SCENE_DEFAULT_QUIET_SECONDS, ir as isDefaultAlarmProfileId, it as NC_CLIP_POST_ROLL_DEFAULT_SEC, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, ji as EventCategory, jn as audioMetricsCapability, jr as ruleAutoTriggersAlarm, jt as NcRulePatchSchema, k as EMBEDDING_MODEL_FAILED, ki as union, kn as applySecureOnArmEdit, kr as resolveModeDelays, kt as NcRuleActionsSchema, l as APPEARANCE_BIN_COUNT, li as hydrateSchema, ln as SetTrackPurityInputSchema, lr as isMediaOwnerType, lt as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, m as AlarmArmModeSchema, mi as ZodObject, mn as TimelapseRuleInputSchema, mr as isVectorIndexDimConflict, mt as NC_SENSOR_TRANSITION_KINDS, ni as DeviceType, nn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, nr as isAlarmControllableCap, nt as NC_AUDIO_CONFIRM_HITS_DEFAULT, o as FACE_EMBEDDING_STEP_ID, oi as createEvent, on as SCENE_DIVERGED, or as isEventKindLabelKey, ot as NC_CLIP_RETENTION_MAX_SEC, p as AUDIO_MACRO_LABELS, pi as sleep$1, pn as TRACK_RELATIONS_DEFAULT_LIMIT, pr as isSystemEventSummaryRule, pt as NC_GROUPING_MIN_SEC, q as MAX_TRACK_RELATIONS, qr as systemEventSummarySplitOf, qt as OpsLogEntrySchema, ri as errMsg, rn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, rr as isAudioClassName, rt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, s as readClusterModelPinStrict, si as customAction, sn as SCENE_RESET_RECAPTURES, sr as isEventOwnerType, st as NC_CONDITION_CATALOG, t as BUILTIN_CLIP_MODELS, ti as isSourceCap, tn as SCENE_DEFAULT_ANCHOR_THRESHOLD, tr as gifFromMp4Args, tt as NC_ALARM_TRIGGERED_MAX_SEC, u as APPEARANCE_BLOCKS, ui as isDeviceScopedCap, un as SetTrackPurityResultSchema, ur as isRetiredClearKey, ut as NC_DEFAULT_SNOOZE_MINUTES, v as BirthDecisionRecordSchema, vi as discriminatedUnion, vn as TrackRelationKindSchema, vr as notificationRulesCapability, vt as NC_TAXONOMY, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wi as record, wn as alarmDeviceSecureOnArm, wr as readMotionTriggeredBy, wt as NcAlarmSettingsPatchSchema, x as ClearTrackRelationResultSchema, xi as number, xn as TrackSourceSchema, xr as pipelineAnalyticsCapability, xt as NcAlarmPostTriggeredActionSchema, y as COCO_TO_MACRO, yi as literal, yn as TrackRelationSchema, yr as occupancyScope, yt as NC_TEMPLATE_VARS, z as LabelAttributionSchema, zn as derivePostTriggeredAction, zr as servedTemplateVars, zt as NcSnoozeSuppressedSchema } from "../clip-model-registry-
|
|
1
|
+
import { $ as NC_ALARM_DELAY_MAX_SEC, $n as failureContributionCapability, $r as canonicalHash, $t as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, A as EMBEDDING_REQUIREMENTS_FAILED, Ai as unknown, An as assertTimelapseCadences, At as NcRuleInputSchema, B as ListTrackRelationsInputSchema, Bn as deriveRecordingMode, Br as storageOccupancyCapability, Bt as NcStateSummarySectionIdSchema, C as DEFAULT_EVENT_COLOR, Ci as partialRecord, Cn as addonWidgetsSourceCapability, Cr as readDeviceStateFrom, Ct as NcAlarmSensorsSchema, D as DeclaredDevices, Di as templateLiteral, Dn as applyModeDelaysEdits, Dr as resolveClipSearchMinScore, Dt as NcLegacyRuleActionSequenceSchema, E as DETECTION_PIPELINE_CAP_NAME, Ei as string, En as applyBlockingEdit, Er as renameAlarmProfile, Et as NcConditionsSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as consumableLowPercentOf, Fr as ruleKindOf, Ft as NcScheduleSchema, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as embeddingRefusalOf, Gr as systemEventFilterApplies, Gt as NcTemplateVarDescriptorSchema, H as LockStateSchema, Hn as describeSystemEventFamilyProblem, Hr as syncActiveAlarmProfileSettings, Ht as NcTaxonomySchema, I as FIRST_LEVEL_MACRO_CLASSES, In as cosineSimilarity$1, Ir as sceneMonitorCapability, It as NcSharedSequenceSchema, J as MOTION_CLOSE_AFTER_MS, Jn as encodeVectorBase64, Jr as templateCarriesSummary, K as MAX_TRACK_PURITIES, Kr as systemEventSummaryScheduleOf, Kt as OPS_LOG_DEFAULT_LIMIT, L as FULL_IMAGE_BBOX, Ln as createAlarmProfile, Lr as secureActuationFor, Lt as NcSnoozeInputSchema, M as EVENT_DENSITY_BATCH_MAX, Mn as audioModeOf, Mr as ruleBelongsToAlarm$1, Mt as NcRuleSchema, N as EVENT_KIND_BY_CAP, Nn as buildEventKindDescriptor, Nr as ruleDeviceCompatCapQueries, Nt as NcRuleTargetSchema, O as DetectionSourceSchema, Oi as tuple, On as applyNonBlockingEdit, Or as resolveLocationMode, Ot as NcRuleActionSchema, P as EVENT_OWNER_TYPES, Pn as cloneAlarmSettings, Pr as ruleGroupingApplies, Pt as NcRuleUpdateInputSchema, Q as NC_ALARM_DELAY_FIELDS, Qn as faceGalleryCapability, Qr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Qt as RetrainStatusSchema, R as FailureCounters, Rn as deleteAlarmProfile, Rr as sensorRaisedOf, Rt as NcSnoozeSchema, S as CoverStateSchema, Si as object, Sn as activateAlarmProfile, Sr as plateGalleryCapability, St as NcAlarmSensorsInputSchema, T as DETECTION_MACRO_CLASSES, Ti as strictObject, Tn as alarmPanelCapability, Tr as readTimelapseGeneratedAt, Tt as NcConditionDescriptorSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Ur as systemEventDeliveryProblems, Ut as NcTemplatePreviewInputSchema, V as ListTrackRelationsResultSchema, Vn as describeSystemEventDeliveryProblem, Vr as subKindsOf, Vt as NcSystemEventKindSchema, W as MAX_BIRTH_DECISION_RECORDS, Wr as systemEventFamilyProblems, Wt as NcTemplatePreviewSchema, X as MotionTriggeredBySchema, Xn as eventKindLabel, Xr as vectorDimFromBase64, Xt as RECORDING_SOURCE_CAMSTACK_ADDON, Y as MediaFileKindEnum, Yn as ensureAlarmProfiles, Yr as templateVarsFor, Yt as RECORDING_EXPORT_MAX_READ_BYTES, Z as NC_ALARM_DEFAULT_PROFILE_ID, Zn as evictionPolicyOfLocation, Zr as zoneAnalyticsCapability, Zt as RebuildObjectEmbeddingsRefusalSchema, _ as BaseDevice, _i as boolean, _n as TrackPuritySchema, _r as memoizePinnedFfmpeg, _t as NC_TANK_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, ai as CamProfileSchema, an as SCENE_DEFAULT_UNCOVERED_POLICY, ar as isDetectionMacroClass, at as NC_CLIP_PRE_ROLL_DEFAULT_SEC, b as ClearTrackRelationInputSchema, bi as looseObject, bn as TrackSideSchema, bt as NcAlarmConfigSchema, c as resolveClusterModelPin, ci as defineCustomActions, cn as SceneMonitorSchema, cr as isKnownTemplateVar, ct as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, di as nodePin, dn as SetTrackRelationInputSchema, dr as isScheduleActive, dt as NC_GROUPING_DEFAULT_SEC, ei as evaluateSensorEdge, en as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, er as gifDeriveSpeed, et as NC_ALARM_SYSTEM_EVENT_KINDS, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fn as TIMELAPSE_DENSE_FLOOR_SEC, fr as isSystemDelivery, ft as NC_GROUPING_MAX_SEC, g as BIRTH_DECISION_DEFAULT_LIMIT, gi as array, gn as TimelapseRuleSchema, gr as mayWriteToLocation, gt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, h as ArchivedDebugNoteSchema, hi as _enum, hn as TimelapseRulePatchSchema, hr as kebabToCamel, ht as NC_SNOOZE_MAX_MINUTES, ii as BaseAddon, in as SCENE_DEFAULT_QUIET_SECONDS, ir as isDefaultAlarmProfileId, it as NC_CLIP_POST_ROLL_DEFAULT_SEC, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, ji as EventCategory, jn as audioMetricsCapability, jr as ruleAutoTriggersAlarm, jt as NcRulePatchSchema, k as EMBEDDING_MODEL_FAILED, ki as union, kn as applySecureOnArmEdit, kr as resolveModeDelays, kt as NcRuleActionsSchema, l as APPEARANCE_BIN_COUNT, li as hydrateSchema, ln as SetTrackPurityInputSchema, lr as isMediaOwnerType, lt as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, m as AlarmArmModeSchema, mi as ZodObject, mn as TimelapseRuleInputSchema, mr as isVectorIndexDimConflict, mt as NC_SENSOR_TRANSITION_KINDS, ni as DeviceType, nn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, nr as isAlarmControllableCap, nt as NC_AUDIO_CONFIRM_HITS_DEFAULT, o as FACE_EMBEDDING_STEP_ID, oi as createEvent, on as SCENE_DIVERGED, or as isEventKindLabelKey, ot as NC_CLIP_RETENTION_MAX_SEC, p as AUDIO_MACRO_LABELS, pi as sleep$1, pn as TRACK_RELATIONS_DEFAULT_LIMIT, pr as isSystemEventSummaryRule, pt as NC_GROUPING_MIN_SEC, q as MAX_TRACK_RELATIONS, qr as systemEventSummarySplitOf, qt as OpsLogEntrySchema, ri as errMsg, rn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, rr as isAudioClassName, rt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, s as readClusterModelPinStrict, si as customAction, sn as SCENE_RESET_RECAPTURES, sr as isEventOwnerType, st as NC_CONDITION_CATALOG, t as BUILTIN_CLIP_MODELS, ti as isSourceCap, tn as SCENE_DEFAULT_ANCHOR_THRESHOLD, tr as gifFromMp4Args, tt as NC_ALARM_TRIGGERED_MAX_SEC, u as APPEARANCE_BLOCKS, ui as isDeviceScopedCap, un as SetTrackPurityResultSchema, ur as isRetiredClearKey, ut as NC_DEFAULT_SNOOZE_MINUTES, v as BirthDecisionRecordSchema, vi as discriminatedUnion, vn as TrackRelationKindSchema, vr as notificationRulesCapability, vt as NC_TAXONOMY, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wi as record, wn as alarmDeviceSecureOnArm, wr as readMotionTriggeredBy, wt as NcAlarmSettingsPatchSchema, x as ClearTrackRelationResultSchema, xi as number, xn as TrackSourceSchema, xr as pipelineAnalyticsCapability, xt as NcAlarmPostTriggeredActionSchema, y as COCO_TO_MACRO, yi as literal, yn as TrackRelationSchema, yr as occupancyScope, yt as NC_TEMPLATE_VARS, z as LabelAttributionSchema, zn as derivePostTriggeredAction, zr as servedTemplateVars, zt as NcSnoozeSuppressedSchema } from "../clip-model-registry-cujWRWxP.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs$1 from "node:fs";
|
|
4
4
|
import { createWriteStream, promises } from "node:fs";
|
|
@@ -363,6 +363,20 @@ function skippedFor(deviceIds, skipProbe) {
|
|
|
363
363
|
return out;
|
|
364
364
|
}
|
|
365
365
|
/**
|
|
366
|
+
* `active` / `skipped` for the arm / arming TITLE — `(n attivi, m saltati)`.
|
|
367
|
+
*
|
|
368
|
+
* Named coverage only: an `allDevices` mode has no finite Active set, so the
|
|
369
|
+
* caller picks the `.all` title key instead of stuffing a fake number here.
|
|
370
|
+
*/
|
|
371
|
+
function armCountParams(coverage, bypassed = []) {
|
|
372
|
+
const skipped = new Set(coverage.skippedDevices.map((s) => s.deviceId));
|
|
373
|
+
const excluded = new Set(bypassed);
|
|
374
|
+
return {
|
|
375
|
+
active: `${coverage.deviceIds.filter((id) => !skipped.has(id) && !excluded.has(id)).length}`,
|
|
376
|
+
skipped: `${skipped.size}`
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
366
380
|
* Whether this mode arms anything, and if not, which way it fails.
|
|
367
381
|
*
|
|
368
382
|
* An `allDevices` mode is never a gap: `skippedDevices` only ever probes the
|
|
@@ -377,28 +391,13 @@ function alarmCoverageGap(coverage) {
|
|
|
377
391
|
if (coverage.skippedDevices.length < coverage.deviceIds.length) return null;
|
|
378
392
|
return "all-skipped";
|
|
379
393
|
}
|
|
394
|
+
/** Device names as a lock-screen bullet list (one name per line). */
|
|
395
|
+
function bulletDeviceNames(names) {
|
|
396
|
+
return names.map((n) => `• ${n}`).join("\n");
|
|
397
|
+
}
|
|
380
398
|
/**
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
* A LIST, not a sentence. The mode is in the title and the body is one
|
|
384
|
-
* `Label: names` line per device group, because this is read on a lock screen
|
|
385
|
-
* and the question is always "which one" — the prose version ("3 dispositivi
|
|
386
|
-
* attivi: …. Esclusi: ….") made the operator parse a paragraph to find a
|
|
387
|
-
* window. Each variant still owns its own catalog key, so the Texts tab can
|
|
388
|
-
* re-word "nothing is armed" without touching "these are armed".
|
|
389
|
-
*
|
|
390
|
-
* Names, not ids: this is read by a person standing at a door. An id that has
|
|
391
|
-
* no name falls back to a NAMED, translated placeholder rather than being
|
|
392
|
-
* dropped — a silently shorter list would understate what is armed, which is
|
|
393
|
-
* the one error this message must not make. (It used to hard-code the English
|
|
394
|
-
* `Device <id>` into both languages.)
|
|
395
|
-
*
|
|
396
|
-
* **Always a sentence.** It used to return null for a mode no rule is gated on,
|
|
397
|
-
* on the grounds that "Night armed" followed by an unwatched night is a false
|
|
398
|
-
* assurance — true, but the caller then emitted `alarm-armed` anyway with a
|
|
399
|
-
* fallback body key that existed in NEITHER locale, so the operator received a
|
|
400
|
-
* title and an empty line. Silence is not the third option here: SAYING that
|
|
401
|
-
* the mode arms nothing is.
|
|
399
|
+
* Skipped / excluded devices FIRST, then active — each name on its own
|
|
400
|
+
* `•` line. The mode is in the title; the body answers "which ones".
|
|
402
401
|
*/
|
|
403
402
|
function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
404
403
|
const rules = `${coverage.ruleCount}`;
|
|
@@ -407,7 +406,11 @@ function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
|
407
406
|
const gap = alarmCoverageGap(coverage);
|
|
408
407
|
if (gap === "uncovered") return {
|
|
409
408
|
bodyKey: "alarm.arm.body.uncovered",
|
|
410
|
-
textParams: {
|
|
409
|
+
textParams: {
|
|
410
|
+
mode,
|
|
411
|
+
active: "0",
|
|
412
|
+
skipped: "0"
|
|
413
|
+
},
|
|
411
414
|
textCount: 0
|
|
412
415
|
};
|
|
413
416
|
if (gap === "all-skipped") {
|
|
@@ -418,7 +421,9 @@ function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
|
418
421
|
rules,
|
|
419
422
|
mode,
|
|
420
423
|
devices: `${names.length}`,
|
|
421
|
-
names: names
|
|
424
|
+
names: bulletDeviceNames(names),
|
|
425
|
+
active: "0",
|
|
426
|
+
skipped: `${names.length}`
|
|
422
427
|
},
|
|
423
428
|
textCount: names.length
|
|
424
429
|
};
|
|
@@ -431,9 +436,10 @@ function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
|
431
436
|
rules,
|
|
432
437
|
mode,
|
|
433
438
|
count: `${names.length}`,
|
|
434
|
-
names: names
|
|
439
|
+
names: bulletDeviceNames(names)
|
|
435
440
|
},
|
|
436
|
-
textCount: names.length
|
|
441
|
+
textCount: names.length,
|
|
442
|
+
titleKeySuffix: "all"
|
|
437
443
|
};
|
|
438
444
|
}
|
|
439
445
|
if (coverage.allDevices) return {
|
|
@@ -442,25 +448,30 @@ function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
|
442
448
|
rules,
|
|
443
449
|
mode
|
|
444
450
|
},
|
|
445
|
-
textCount: coverage.ruleCount
|
|
451
|
+
textCount: coverage.ruleCount,
|
|
452
|
+
titleKeySuffix: "all"
|
|
446
453
|
};
|
|
447
454
|
if (coverage.deviceIds.length === 0) return {
|
|
448
455
|
bodyKey: "alarm.arm.body.noDevices",
|
|
449
456
|
textParams: {
|
|
450
457
|
rules,
|
|
451
|
-
mode
|
|
458
|
+
mode,
|
|
459
|
+
active: "0",
|
|
460
|
+
skipped: "0"
|
|
452
461
|
},
|
|
453
462
|
textCount: coverage.ruleCount
|
|
454
463
|
};
|
|
455
464
|
const skipped = new Set(coverage.skippedDevices.map((s) => s.deviceId));
|
|
456
465
|
const names = coverage.deviceIds.filter((id) => !skipped.has(id)).map(deviceName);
|
|
466
|
+
const counts = armCountParams(coverage);
|
|
457
467
|
if (skipped.size === 0) return {
|
|
458
468
|
bodyKey: "alarm.arm.body.devices",
|
|
459
469
|
textParams: {
|
|
460
470
|
rules,
|
|
461
471
|
mode,
|
|
462
472
|
devices: `${names.length}`,
|
|
463
|
-
names: names
|
|
473
|
+
names: bulletDeviceNames(names),
|
|
474
|
+
...counts
|
|
464
475
|
},
|
|
465
476
|
textCount: names.length
|
|
466
477
|
};
|
|
@@ -470,8 +481,9 @@ function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
|
470
481
|
rules,
|
|
471
482
|
mode,
|
|
472
483
|
devices: `${names.length}`,
|
|
473
|
-
names: names
|
|
474
|
-
skippedNames: coverage.skippedDevices.map((s) => deviceName(s.deviceId))
|
|
484
|
+
names: bulletDeviceNames(names),
|
|
485
|
+
skippedNames: bulletDeviceNames(coverage.skippedDevices.map((s) => deviceName(s.deviceId))),
|
|
486
|
+
...counts
|
|
475
487
|
},
|
|
476
488
|
textCount: names.length
|
|
477
489
|
};
|
|
@@ -486,7 +498,7 @@ function buildArmAnnouncement(coverage, deviceName, bypassed = []) {
|
|
|
486
498
|
function armedWithBypass(coverage, bypassed, deviceName) {
|
|
487
499
|
const skipped = new Set(coverage.skippedDevices.map((s) => s.deviceId));
|
|
488
500
|
const excluded = new Set(bypassed);
|
|
489
|
-
const bypassedNames = bypassed.map(deviceName)
|
|
501
|
+
const bypassedNames = bulletDeviceNames(bypassed.map(deviceName));
|
|
490
502
|
if (coverage.allDevices) return {
|
|
491
503
|
bodyKey: "alarm.arm.body.bypassedAll",
|
|
492
504
|
textParams: {
|
|
@@ -494,10 +506,12 @@ function armedWithBypass(coverage, bypassed, deviceName) {
|
|
|
494
506
|
mode: coverage.mode,
|
|
495
507
|
bypassedNames
|
|
496
508
|
},
|
|
497
|
-
textCount: coverage.ruleCount
|
|
509
|
+
textCount: coverage.ruleCount,
|
|
510
|
+
titleKeySuffix: "all"
|
|
498
511
|
};
|
|
499
512
|
const armedIds = coverage.deviceIds.filter((id) => !skipped.has(id) && !excluded.has(id));
|
|
500
|
-
const names = armedIds.map(deviceName)
|
|
513
|
+
const names = bulletDeviceNames(armedIds.map(deviceName));
|
|
514
|
+
const counts = armCountParams(coverage, bypassed);
|
|
501
515
|
return {
|
|
502
516
|
bodyKey: "alarm.arm.body.bypassed",
|
|
503
517
|
textParams: {
|
|
@@ -505,7 +519,8 @@ function armedWithBypass(coverage, bypassed, deviceName) {
|
|
|
505
519
|
mode: coverage.mode,
|
|
506
520
|
devices: `${armedIds.length}`,
|
|
507
521
|
names,
|
|
508
|
-
bypassedNames
|
|
522
|
+
bypassedNames,
|
|
523
|
+
...counts
|
|
509
524
|
},
|
|
510
525
|
textCount: armedIds.length
|
|
511
526
|
};
|
|
@@ -526,69 +541,159 @@ function buildArmRefusedAnnouncement(mode, names, withArmAnyway) {
|
|
|
526
541
|
textParams: {
|
|
527
542
|
mode,
|
|
528
543
|
devices: `${names.length}`,
|
|
529
|
-
names: names
|
|
544
|
+
names: bulletDeviceNames(names)
|
|
530
545
|
},
|
|
531
546
|
textCount: names.length
|
|
532
547
|
};
|
|
533
548
|
}
|
|
534
549
|
/**
|
|
535
|
-
* The
|
|
550
|
+
* The same coverage lists as {@link buildArmAnnouncement}, said at second 0.
|
|
536
551
|
*
|
|
537
|
-
* The
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
* **It never repeats the device list.** The countdown lives in the TITLE
|
|
544
|
-
* (`system.alarm-arming.title.<mode>` renders `{{seconds}}`), and the armed
|
|
545
|
-
* list arrives thirty seconds later in the arm announcement — printing it
|
|
546
|
-
* twice is the verbosity that made the operator stop reading both. So the body
|
|
547
|
-
* is what is WRONG: what was bypassed, what is excluded, or that nothing is.
|
|
552
|
+
* The countdown lives in the TITLE (`system.alarm-arming.title.<mode>` renders
|
|
553
|
+
* `{{seconds}}`); the body is the labelled device lists — Active and Skipped
|
|
554
|
+
* (and Bypassed when the operator confirmed an open sensor). Saying the lists
|
|
555
|
+
* only at second 30 left the exit-delay push as "No exclusions" / a lone
|
|
556
|
+
* Esclusi line, which is exactly when the operator can still disarm and fix
|
|
557
|
+
* a mute or an offline camera.
|
|
548
558
|
*
|
|
549
559
|
* `seconds` stays in the params because the title asks for it, and `textCount`
|
|
550
560
|
* stays with it: a row enqueued before this deploy still pluralises on the
|
|
551
561
|
* countdown it was written for.
|
|
552
562
|
*/
|
|
553
563
|
function buildArmingAnnouncement(coverage, seconds, deviceName, bypassed = []) {
|
|
564
|
+
const mode = coverage.mode;
|
|
565
|
+
const rules = `${coverage.ruleCount}`;
|
|
554
566
|
const textParams = {
|
|
555
|
-
mode
|
|
567
|
+
mode,
|
|
556
568
|
seconds: `${seconds}`
|
|
557
569
|
};
|
|
558
|
-
if (bypassed.length > 0) return
|
|
559
|
-
|
|
570
|
+
if (bypassed.length > 0) return armingWithBypass(coverage, seconds, bypassed, deviceName);
|
|
571
|
+
const gap = alarmCoverageGap(coverage);
|
|
572
|
+
if (gap === "uncovered") return {
|
|
573
|
+
bodyKey: "alarm.arming.body.uncovered",
|
|
560
574
|
textParams: {
|
|
561
575
|
...textParams,
|
|
562
|
-
|
|
576
|
+
active: "0",
|
|
577
|
+
skipped: "0"
|
|
563
578
|
},
|
|
564
579
|
textCount: seconds
|
|
565
580
|
};
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
581
|
+
if (gap === "all-skipped") {
|
|
582
|
+
const names = coverage.skippedDevices.map((s) => deviceName(s.deviceId));
|
|
583
|
+
return {
|
|
584
|
+
bodyKey: "alarm.arming.body.allSkipped",
|
|
585
|
+
textParams: {
|
|
586
|
+
...textParams,
|
|
587
|
+
rules,
|
|
588
|
+
devices: `${names.length}`,
|
|
589
|
+
names: bulletDeviceNames(names),
|
|
590
|
+
active: "0",
|
|
591
|
+
skipped: `${names.length}`
|
|
592
|
+
},
|
|
593
|
+
textCount: seconds
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
if (coverage.allDevices && coverage.allDevicesExcept.length > 0) {
|
|
597
|
+
const names = coverage.allDevicesExcept.map(deviceName);
|
|
598
|
+
return {
|
|
599
|
+
bodyKey: "alarm.arming.body.allDevicesExcept",
|
|
600
|
+
textParams: {
|
|
601
|
+
...textParams,
|
|
602
|
+
rules,
|
|
603
|
+
count: `${names.length}`,
|
|
604
|
+
names: bulletDeviceNames(names)
|
|
605
|
+
},
|
|
606
|
+
textCount: seconds,
|
|
607
|
+
titleKeySuffix: "all"
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
if (coverage.allDevices) return {
|
|
611
|
+
bodyKey: "alarm.arming.body.allDevices",
|
|
612
|
+
textParams: {
|
|
613
|
+
...textParams,
|
|
614
|
+
rules
|
|
615
|
+
},
|
|
616
|
+
textCount: seconds,
|
|
617
|
+
titleKeySuffix: "all"
|
|
572
618
|
};
|
|
573
|
-
if (
|
|
574
|
-
bodyKey: "alarm.arming.body.
|
|
619
|
+
if (coverage.deviceIds.length === 0) return {
|
|
620
|
+
bodyKey: "alarm.arming.body.noDevices",
|
|
575
621
|
textParams: {
|
|
576
622
|
...textParams,
|
|
577
|
-
|
|
623
|
+
rules,
|
|
624
|
+
active: "0",
|
|
625
|
+
skipped: "0"
|
|
578
626
|
},
|
|
579
627
|
textCount: seconds
|
|
580
628
|
};
|
|
581
|
-
|
|
582
|
-
|
|
629
|
+
const skipped = new Set(coverage.skippedDevices.map((s) => s.deviceId));
|
|
630
|
+
const names = coverage.deviceIds.filter((id) => !skipped.has(id)).map(deviceName);
|
|
631
|
+
const counts = armCountParams(coverage);
|
|
632
|
+
if (skipped.size === 0) return {
|
|
633
|
+
bodyKey: "alarm.arming.body.devices",
|
|
583
634
|
textParams: {
|
|
584
635
|
...textParams,
|
|
585
|
-
|
|
636
|
+
rules,
|
|
637
|
+
devices: `${names.length}`,
|
|
638
|
+
names: bulletDeviceNames(names),
|
|
639
|
+
...counts
|
|
586
640
|
},
|
|
587
|
-
textCount:
|
|
641
|
+
textCount: seconds
|
|
588
642
|
};
|
|
589
643
|
return {
|
|
590
|
-
bodyKey: "alarm.arming.body",
|
|
644
|
+
bodyKey: "alarm.arming.body.devicesSkipped",
|
|
645
|
+
textParams: {
|
|
646
|
+
...textParams,
|
|
647
|
+
rules,
|
|
648
|
+
devices: `${names.length}`,
|
|
649
|
+
names: bulletDeviceNames(names),
|
|
650
|
+
skippedNames: bulletDeviceNames(coverage.skippedDevices.map((s) => deviceName(s.deviceId))),
|
|
651
|
+
...counts
|
|
652
|
+
},
|
|
653
|
+
textCount: seconds
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* "Attivi: Porta retro, Cancello — Bypassati: Finestra veranda" while the exit
|
|
658
|
+
* delay still runs — same halves as {@link armedWithBypass}, with `seconds`
|
|
659
|
+
* frozen for the title.
|
|
660
|
+
*/
|
|
661
|
+
function armingWithBypass(coverage, seconds, bypassed, deviceName) {
|
|
662
|
+
const skipped = new Set(coverage.skippedDevices.map((s) => s.deviceId));
|
|
663
|
+
const excluded = new Set(bypassed);
|
|
664
|
+
const bypassedNames = bulletDeviceNames(bypassed.map(deviceName));
|
|
665
|
+
const textParams = {
|
|
666
|
+
mode: coverage.mode,
|
|
667
|
+
seconds: `${seconds}`,
|
|
668
|
+
rules: `${coverage.ruleCount}`,
|
|
669
|
+
bypassedNames
|
|
670
|
+
};
|
|
671
|
+
if (coverage.allDevices) return {
|
|
672
|
+
bodyKey: "alarm.arming.body.bypassedAll",
|
|
591
673
|
textParams,
|
|
674
|
+
textCount: seconds,
|
|
675
|
+
titleKeySuffix: "all"
|
|
676
|
+
};
|
|
677
|
+
const armedIds = coverage.deviceIds.filter((id) => !skipped.has(id) && !excluded.has(id));
|
|
678
|
+
const counts = armCountParams(coverage, bypassed);
|
|
679
|
+
if (armedIds.length === 0) return {
|
|
680
|
+
bodyKey: "alarm.arming.body.bypassedOnly",
|
|
681
|
+
textParams: {
|
|
682
|
+
mode: coverage.mode,
|
|
683
|
+
seconds: `${seconds}`,
|
|
684
|
+
bypassedNames,
|
|
685
|
+
...counts
|
|
686
|
+
},
|
|
687
|
+
textCount: seconds
|
|
688
|
+
};
|
|
689
|
+
return {
|
|
690
|
+
bodyKey: "alarm.arming.body.bypassed",
|
|
691
|
+
textParams: {
|
|
692
|
+
...textParams,
|
|
693
|
+
devices: `${armedIds.length}`,
|
|
694
|
+
names: bulletDeviceNames(armedIds.map(deviceName)),
|
|
695
|
+
...counts
|
|
696
|
+
},
|
|
592
697
|
textCount: seconds
|
|
593
698
|
};
|
|
594
699
|
}
|
|
@@ -3211,12 +3316,23 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3211
3316
|
* dropped it anyway, so the exclusion is not what ignored it.
|
|
3212
3317
|
*/
|
|
3213
3318
|
announceIgnoredTrigger(deviceId, now) {
|
|
3319
|
+
const state = stateAt(this.machine, now);
|
|
3214
3320
|
if (!isArmed(this.machine, now)) {
|
|
3215
3321
|
this.ctx.logger.debug("alarm bypass not announced — the panel is not armed yet", {
|
|
3216
3322
|
tags: { deviceId },
|
|
3217
3323
|
meta: {
|
|
3218
3324
|
panelDeviceId: this.id,
|
|
3219
|
-
state
|
|
3325
|
+
state
|
|
3326
|
+
}
|
|
3327
|
+
});
|
|
3328
|
+
return;
|
|
3329
|
+
}
|
|
3330
|
+
if (state === "triggered") {
|
|
3331
|
+
this.ctx.logger.debug("alarm bypass not announced — the siren is already sounding", {
|
|
3332
|
+
tags: { deviceId },
|
|
3333
|
+
meta: {
|
|
3334
|
+
panelDeviceId: this.id,
|
|
3335
|
+
state
|
|
3220
3336
|
}
|
|
3221
3337
|
});
|
|
3222
3338
|
return;
|
|
@@ -5921,7 +6037,7 @@ function isAdoptable(record, nowMs) {
|
|
|
5921
6037
|
* what `localDayMinute` does for the same input — one behaviour for a bad
|
|
5922
6038
|
* zone, not two.
|
|
5923
6039
|
*/
|
|
5924
|
-
function clockOf$
|
|
6040
|
+
function clockOf$6(atMs, timeZone) {
|
|
5925
6041
|
if (timeZone !== void 0) try {
|
|
5926
6042
|
return new Intl.DateTimeFormat("en-GB", {
|
|
5927
6043
|
timeZone,
|
|
@@ -6057,7 +6173,7 @@ var TimelapseRenderer = class {
|
|
|
6057
6173
|
includeAudio: false,
|
|
6058
6174
|
maxLifeMs: await this.ports.exportLifeMs(),
|
|
6059
6175
|
deleteAfterDownload: false,
|
|
6060
|
-
title: `${request.ruleName} ${clockOf$
|
|
6176
|
+
title: `${request.ruleName} ${clockOf$6(window.startMs, request.timezone)}→${clockOf$6(window.endMs, request.timezone)}`,
|
|
6061
6177
|
timelapse: {
|
|
6062
6178
|
everyMs,
|
|
6063
6179
|
outputFps: request.framerate,
|
|
@@ -6388,7 +6504,7 @@ var TimelapseRenderer = class {
|
|
|
6388
6504
|
...meta,
|
|
6389
6505
|
unservedWindowCount: verdict.unservedWindows.length,
|
|
6390
6506
|
unservedWindows: verdict.unservedWindows.slice(0, 12).map((w) => ({
|
|
6391
|
-
at: clockOf$
|
|
6507
|
+
at: clockOf$6(request.window.startMs + w.fromSec * 1e3, request.timezone),
|
|
6392
6508
|
fromSec: Math.round(w.fromSec),
|
|
6393
6509
|
toSec: Math.round(w.toSec),
|
|
6394
6510
|
expectedDenseFrames: w.expectedDenseFrames,
|
|
@@ -13056,7 +13172,8 @@ function alarmTextKeys(transition, body) {
|
|
|
13056
13172
|
const { kind, source } = transition;
|
|
13057
13173
|
const combined = combinedAlarmText(kind, source);
|
|
13058
13174
|
if (combined !== null) return combined;
|
|
13059
|
-
const
|
|
13175
|
+
const baseTitle = transition.mode !== void 0 ? `system.${kind}.title.${transition.mode}` : `system.${kind}.title`;
|
|
13176
|
+
const titleKey = body?.titleKeySuffix !== void 0 ? `${baseTitle}.${body.titleKeySuffix}` : baseTitle;
|
|
13060
13177
|
if (body !== void 0) return {
|
|
13061
13178
|
titleKey,
|
|
13062
13179
|
bodyKey: body.bodyKey
|
|
@@ -14763,34 +14880,47 @@ var en_default = {
|
|
|
14763
14880
|
"snooze.scope.anyRule": "A rule",
|
|
14764
14881
|
"snooze.scope.rule": "“{{rule}}”",
|
|
14765
14882
|
"alarm.arm.body.allDevices": "Active: all devices",
|
|
14766
|
-
"alarm.arm.body.allDevicesExcept": "Active: all devices except
|
|
14883
|
+
"alarm.arm.body.allDevicesExcept": "Active: all devices except\n{{names}}",
|
|
14767
14884
|
"alarm.arm.body.noDevices": "Active: no device restriction",
|
|
14768
|
-
"alarm.arm.body.devices": "Active
|
|
14769
|
-
"alarm.arm.body.devicesSkipped": "
|
|
14885
|
+
"alarm.arm.body.devices": "Active:\n{{names}}",
|
|
14886
|
+
"alarm.arm.body.devicesSkipped": "Excluded:\n{{skippedNames}}\nActive:\n{{names}}",
|
|
14770
14887
|
"alarm.arm.body.uncovered": "Active: none — no rule uses this mode",
|
|
14771
|
-
"alarm.arm.body.allSkipped": "
|
|
14888
|
+
"alarm.arm.body.allSkipped": "Excluded:\n{{names}}\nActive: none",
|
|
14772
14889
|
"alarm.arming.body": "No exclusions",
|
|
14890
|
+
"alarm.arming.body.allDevices": "Active: all devices",
|
|
14891
|
+
"alarm.arming.body.allDevicesExcept": "Active: all devices except\n{{names}}",
|
|
14892
|
+
"alarm.arming.body.noDevices": "Active: no device restriction",
|
|
14893
|
+
"alarm.arming.body.devices": "Active:\n{{names}}",
|
|
14894
|
+
"alarm.arming.body.devicesSkipped": "Excluded:\n{{skippedNames}}\nActive:\n{{names}}",
|
|
14773
14895
|
"alarm.arming.body.uncovered": "Active: none — no rule uses this mode",
|
|
14774
|
-
"alarm.arming.body.allSkipped": "
|
|
14775
|
-
"alarm.arming.body.skipped": "Excluded
|
|
14776
|
-
"alarm.arm.body.bypassed": "
|
|
14777
|
-
"alarm.arm.body.bypassedAll": "
|
|
14778
|
-
"alarm.arming.body.bypassed": "Bypassed
|
|
14779
|
-
"alarm.
|
|
14780
|
-
"alarm.
|
|
14896
|
+
"alarm.arming.body.allSkipped": "Excluded:\n{{names}}\nActive: none",
|
|
14897
|
+
"alarm.arming.body.skipped": "Excluded:\n{{names}}",
|
|
14898
|
+
"alarm.arm.body.bypassed": "Bypassed:\n{{bypassedNames}}\nActive:\n{{names}}",
|
|
14899
|
+
"alarm.arm.body.bypassedAll": "Bypassed:\n{{bypassedNames}}\nActive: all devices",
|
|
14900
|
+
"alarm.arming.body.bypassed": "Bypassed:\n{{bypassedNames}}\nActive:\n{{names}}",
|
|
14901
|
+
"alarm.arming.body.bypassedAll": "Bypassed:\n{{bypassedNames}}\nActive: all devices",
|
|
14902
|
+
"alarm.arming.body.bypassedOnly": "Bypassed:\n{{bypassedNames}}",
|
|
14903
|
+
"alarm.armRefused.body": "Open:\n{{names}}",
|
|
14904
|
+
"alarm.armRefused.body.hint": "Open:\n{{names}}\nTap “Arm anyway” to arm with them excluded.",
|
|
14781
14905
|
"alarm.device.unnamed": "Device {{id}}",
|
|
14782
14906
|
"alarm.combined.title": "Alarm: {{deviceName}}",
|
|
14783
14907
|
"system.alarm-arm-refused.title.home": "Arming home refused",
|
|
14784
14908
|
"system.alarm-arm-refused.title.away": "Arming away refused",
|
|
14785
14909
|
"system.alarm-arm-refused.title.night": "Arming night refused",
|
|
14786
14910
|
"system.alarm-arm-refused.body": "Arming refused",
|
|
14787
|
-
"system.alarm-arming.title.home": "Arming home — {{seconds}}s",
|
|
14788
|
-
"system.alarm-arming.title.away": "Arming away — {{seconds}}s",
|
|
14789
|
-
"system.alarm-arming.title.night": "Arming night — {{seconds}}s",
|
|
14911
|
+
"system.alarm-arming.title.home": "Arming home — {{seconds}}s ({{active}} active, {{skipped}} skipped)",
|
|
14912
|
+
"system.alarm-arming.title.away": "Arming away — {{seconds}}s ({{active}} active, {{skipped}} skipped)",
|
|
14913
|
+
"system.alarm-arming.title.night": "Arming night — {{seconds}}s ({{active}} active, {{skipped}} skipped)",
|
|
14914
|
+
"system.alarm-arming.title.home.all": "Arming home — {{seconds}}s",
|
|
14915
|
+
"system.alarm-arming.title.away.all": "Arming away — {{seconds}}s",
|
|
14916
|
+
"system.alarm-arming.title.night.all": "Arming night — {{seconds}}s",
|
|
14790
14917
|
"system.alarm-arming.body": "Arming",
|
|
14791
|
-
"system.alarm-armed.title.home": "Alarm
|
|
14792
|
-
"system.alarm-armed.title.away": "Alarm
|
|
14793
|
-
"system.alarm-armed.title.night": "Alarm
|
|
14918
|
+
"system.alarm-armed.title.home": "Alarm active: home ({{active}} active, {{skipped}} skipped)",
|
|
14919
|
+
"system.alarm-armed.title.away": "Alarm active: away ({{active}} active, {{skipped}} skipped)",
|
|
14920
|
+
"system.alarm-armed.title.night": "Alarm active: night ({{active}} active, {{skipped}} skipped)",
|
|
14921
|
+
"system.alarm-armed.title.home.all": "Alarm active: home (all)",
|
|
14922
|
+
"system.alarm-armed.title.away.all": "Alarm active: away (all)",
|
|
14923
|
+
"system.alarm-armed.title.night.all": "Alarm active: night (all)",
|
|
14794
14924
|
"system.alarm-armed.body": "Alarm armed",
|
|
14795
14925
|
"system.alarm-disarmed.title": "Disarmed",
|
|
14796
14926
|
"system.alarm-disarmed.body": "Alarm disarmed",
|
|
@@ -14888,7 +15018,16 @@ var en_default = {
|
|
|
14888
15018
|
"summary.line.update": "{{packageName}} {{currentVersion}} → {{latestVersion}}",
|
|
14889
15019
|
"summary.line.update.onNode": "{{packageName}} {{currentVersion}} → {{latestVersion}} on {{nodeId}}",
|
|
14890
15020
|
"summary.line.wrapper-update": "Container {{currentVersion}} → {{latestVersion}}",
|
|
14891
|
-
"summary.line.tank-unread": "{{deviceName}} — the {{tank}} tank needs attention"
|
|
15021
|
+
"summary.line.tank-unread": "{{deviceName}} — the {{tank}} tank needs attention",
|
|
15022
|
+
"calendar-events.title": "{{rule}}: {{count}} upcoming",
|
|
15023
|
+
"calendar-events.empty": "No events in the next {{horizonDays}} day(s).",
|
|
15024
|
+
"calendar-events.day.today": "Today — {{date}}",
|
|
15025
|
+
"calendar-events.day.tomorrow": "★ Tomorrow — {{date}}",
|
|
15026
|
+
"calendar-events.day.other": "{{date}}",
|
|
15027
|
+
"calendar-events.line.timed": "• {{when}} {{title}}{{where}}",
|
|
15028
|
+
"calendar-events.line.allDay": "• All day {{title}}{{where}}",
|
|
15029
|
+
"calendar-events.where": " @ {{location}}",
|
|
15030
|
+
"calendar-events.more": "…and {{count}} more"
|
|
14892
15031
|
};
|
|
14893
15032
|
//#endregion
|
|
14894
15033
|
//#region src/notification-center/texts/it.json
|
|
@@ -15092,34 +15231,47 @@ var it_default = {
|
|
|
15092
15231
|
"snooze.scope.anyRule": "Una regola",
|
|
15093
15232
|
"snooze.scope.rule": "“{{rule}}”",
|
|
15094
15233
|
"alarm.arm.body.allDevices": "Attivi: tutti i dispositivi",
|
|
15095
|
-
"alarm.arm.body.allDevicesExcept": "Attivi: tutti i dispositivi tranne
|
|
15234
|
+
"alarm.arm.body.allDevicesExcept": "Attivi: tutti i dispositivi tranne\n{{names}}",
|
|
15096
15235
|
"alarm.arm.body.noDevices": "Attivi: nessuna restrizione",
|
|
15097
|
-
"alarm.arm.body.devices": "Attivi
|
|
15098
|
-
"alarm.arm.body.devicesSkipped": "
|
|
15236
|
+
"alarm.arm.body.devices": "Attivi:\n{{names}}",
|
|
15237
|
+
"alarm.arm.body.devicesSkipped": "Esclusi:\n{{skippedNames}}\nAttivi:\n{{names}}",
|
|
15099
15238
|
"alarm.arm.body.uncovered": "Attivi: nessuno — nessuna regola usa questa modalità",
|
|
15100
|
-
"alarm.arm.body.allSkipped": "
|
|
15239
|
+
"alarm.arm.body.allSkipped": "Esclusi:\n{{names}}\nAttivi: nessuno",
|
|
15101
15240
|
"alarm.arming.body": "Nessuna esclusione",
|
|
15241
|
+
"alarm.arming.body.allDevices": "Attivi: tutti i dispositivi",
|
|
15242
|
+
"alarm.arming.body.allDevicesExcept": "Attivi: tutti i dispositivi tranne\n{{names}}",
|
|
15243
|
+
"alarm.arming.body.noDevices": "Attivi: nessuna restrizione",
|
|
15244
|
+
"alarm.arming.body.devices": "Attivi:\n{{names}}",
|
|
15245
|
+
"alarm.arming.body.devicesSkipped": "Esclusi:\n{{skippedNames}}\nAttivi:\n{{names}}",
|
|
15102
15246
|
"alarm.arming.body.uncovered": "Attivi: nessuno — nessuna regola usa questa modalità",
|
|
15103
|
-
"alarm.arming.body.allSkipped": "
|
|
15104
|
-
"alarm.arming.body.skipped": "Esclusi
|
|
15105
|
-
"alarm.arm.body.bypassed": "
|
|
15106
|
-
"alarm.arm.body.bypassedAll": "
|
|
15107
|
-
"alarm.arming.body.bypassed": "Bypassati
|
|
15108
|
-
"alarm.
|
|
15109
|
-
"alarm.
|
|
15247
|
+
"alarm.arming.body.allSkipped": "Esclusi:\n{{names}}\nAttivi: nessuno",
|
|
15248
|
+
"alarm.arming.body.skipped": "Esclusi:\n{{names}}",
|
|
15249
|
+
"alarm.arm.body.bypassed": "Bypassati:\n{{bypassedNames}}\nAttivi:\n{{names}}",
|
|
15250
|
+
"alarm.arm.body.bypassedAll": "Bypassati:\n{{bypassedNames}}\nAttivi: tutti i dispositivi",
|
|
15251
|
+
"alarm.arming.body.bypassed": "Bypassati:\n{{bypassedNames}}\nAttivi:\n{{names}}",
|
|
15252
|
+
"alarm.arming.body.bypassedAll": "Bypassati:\n{{bypassedNames}}\nAttivi: tutti i dispositivi",
|
|
15253
|
+
"alarm.arming.body.bypassedOnly": "Bypassati:\n{{bypassedNames}}",
|
|
15254
|
+
"alarm.armRefused.body": "Aperti:\n{{names}}",
|
|
15255
|
+
"alarm.armRefused.body.hint": "Aperti:\n{{names}}\nTocca “Arma comunque” per armare escludendoli.",
|
|
15110
15256
|
"alarm.device.unnamed": "Dispositivo {{id}}",
|
|
15111
15257
|
"alarm.combined.title": "Allarme: {{deviceName}}",
|
|
15112
15258
|
"system.alarm-arm-refused.title.home": "Inserimento Casa rifiutato",
|
|
15113
15259
|
"system.alarm-arm-refused.title.away": "Inserimento Fuori rifiutato",
|
|
15114
15260
|
"system.alarm-arm-refused.title.night": "Inserimento Notte rifiutato",
|
|
15115
15261
|
"system.alarm-arm-refused.body": "Inserimento rifiutato",
|
|
15116
|
-
"system.alarm-arming.title.home": "Inserimento Casa — {{seconds}}s",
|
|
15117
|
-
"system.alarm-arming.title.away": "Inserimento Fuori — {{seconds}}s",
|
|
15118
|
-
"system.alarm-arming.title.night": "Inserimento Notte — {{seconds}}s",
|
|
15262
|
+
"system.alarm-arming.title.home": "Inserimento Casa — {{seconds}}s ({{active}} attivi, {{skipped}} saltati)",
|
|
15263
|
+
"system.alarm-arming.title.away": "Inserimento Fuori — {{seconds}}s ({{active}} attivi, {{skipped}} saltati)",
|
|
15264
|
+
"system.alarm-arming.title.night": "Inserimento Notte — {{seconds}}s ({{active}} attivi, {{skipped}} saltati)",
|
|
15265
|
+
"system.alarm-arming.title.home.all": "Inserimento Casa — {{seconds}}s",
|
|
15266
|
+
"system.alarm-arming.title.away.all": "Inserimento Fuori — {{seconds}}s",
|
|
15267
|
+
"system.alarm-arming.title.night.all": "Inserimento Notte — {{seconds}}s",
|
|
15119
15268
|
"system.alarm-arming.body": "Inserimento in corso",
|
|
15120
|
-
"system.alarm-armed.title.home": "Allarme
|
|
15121
|
-
"system.alarm-armed.title.away": "Allarme
|
|
15122
|
-
"system.alarm-armed.title.night": "Allarme
|
|
15269
|
+
"system.alarm-armed.title.home": "Allarme attivo: Casa ({{active}} attivi, {{skipped}} saltati)",
|
|
15270
|
+
"system.alarm-armed.title.away": "Allarme attivo: Fuori ({{active}} attivi, {{skipped}} saltati)",
|
|
15271
|
+
"system.alarm-armed.title.night": "Allarme attivo: Notte ({{active}} attivi, {{skipped}} saltati)",
|
|
15272
|
+
"system.alarm-armed.title.home.all": "Allarme attivo: Casa (tutti)",
|
|
15273
|
+
"system.alarm-armed.title.away.all": "Allarme attivo: Fuori (tutti)",
|
|
15274
|
+
"system.alarm-armed.title.night.all": "Allarme attivo: Notte (tutti)",
|
|
15123
15275
|
"system.alarm-armed.body": "Allarme inserito",
|
|
15124
15276
|
"system.alarm-disarmed.title": "Disinserito",
|
|
15125
15277
|
"system.alarm-disarmed.body": "Allarme disinserito",
|
|
@@ -15217,7 +15369,16 @@ var it_default = {
|
|
|
15217
15369
|
"summary.line.update": "{{packageName}} {{currentVersion}} → {{latestVersion}}",
|
|
15218
15370
|
"summary.line.update.onNode": "{{packageName}} {{currentVersion}} → {{latestVersion}} su {{nodeId}}",
|
|
15219
15371
|
"summary.line.wrapper-update": "Container {{currentVersion}} → {{latestVersion}}",
|
|
15220
|
-
"summary.line.tank-unread": "{{deviceName}} — il serbatoio {{tank}} richiede attenzione"
|
|
15372
|
+
"summary.line.tank-unread": "{{deviceName}} — il serbatoio {{tank}} richiede attenzione",
|
|
15373
|
+
"calendar-events.title": "{{rule}}: {{count}} in arrivo",
|
|
15374
|
+
"calendar-events.empty": "Nessun evento nei prossimi {{horizonDays}} giorno/i.",
|
|
15375
|
+
"calendar-events.day.today": "Oggi — {{date}}",
|
|
15376
|
+
"calendar-events.day.tomorrow": "★ Domani — {{date}}",
|
|
15377
|
+
"calendar-events.day.other": "{{date}}",
|
|
15378
|
+
"calendar-events.line.timed": "• {{when}} {{title}}{{where}}",
|
|
15379
|
+
"calendar-events.line.allDay": "• Tutto il giorno {{title}}{{where}}",
|
|
15380
|
+
"calendar-events.where": " @ {{location}}",
|
|
15381
|
+
"calendar-events.more": "…e altri {{count}}"
|
|
15221
15382
|
};
|
|
15222
15383
|
//#endregion
|
|
15223
15384
|
//#region src/notification-center/text-catalog.ts
|
|
@@ -15471,12 +15632,12 @@ var NC_RULE_AI_WORDS_FALLBACK = 24;
|
|
|
15471
15632
|
*
|
|
15472
15633
|
* | kind (declared `bodyMaxLen`) | words |
|
|
15473
15634
|
* | --- | --- |
|
|
15474
|
-
* |
|
|
15635
|
+
* | (legacy 500-char push) | 12 |
|
|
15475
15636
|
* | pushover (1024) | 24 |
|
|
15476
15637
|
* | discord (2000) | 36 |
|
|
15477
15638
|
* | home-assistant (3500) | 46 |
|
|
15478
15639
|
* | ntfy · gotify · telegram · whatsapp (4096) | 48 |
|
|
15479
|
-
* | webhook (1 000 000) | 120 |
|
|
15640
|
+
* | webhook · zentik (1 000 000) | 120 |
|
|
15480
15641
|
*
|
|
15481
15642
|
* Which is the operator's sentence, arrived at from a declared number: a push
|
|
15482
15643
|
* notification is truncated by the screen and wants few words; a webhook or a
|
|
@@ -29821,7 +29982,7 @@ function buildRuleAiSystemPrompt(shape, language, maxWords, grouped = false) {
|
|
|
29821
29982
|
].join("");
|
|
29822
29983
|
}
|
|
29823
29984
|
/** `HH:MM` in the host timezone. */
|
|
29824
|
-
function clockOf$
|
|
29985
|
+
function clockOf$5(atMs) {
|
|
29825
29986
|
const d = new Date(atMs);
|
|
29826
29987
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
29827
29988
|
}
|
|
@@ -29859,7 +30020,7 @@ function buildRuleAiPrompt(input) {
|
|
|
29859
30020
|
if (shape === "detection-track") {
|
|
29860
30021
|
lines.push(`Camera: ${camera}.${located} Subject: ${subject}. The stills, in order:`);
|
|
29861
30022
|
for (const [i, frame] of input.frames.entries()) {
|
|
29862
|
-
const when = frame.atMs !== void 0 ? ` · ${clockOf$
|
|
30023
|
+
const when = frame.atMs !== void 0 ? ` · ${clockOf$5(frame.atMs)}` : "";
|
|
29863
30024
|
lines.push(`${String(i + 1)}.${when}`);
|
|
29864
30025
|
}
|
|
29865
30026
|
return lines.join("\n");
|
|
@@ -31630,7 +31791,7 @@ var DEFAULT_PROMPT_EN = "What happened during this period? Summarise it for a ho
|
|
|
31630
31791
|
*/
|
|
31631
31792
|
var SummaryAnswerSchema = object({ summary: string() });
|
|
31632
31793
|
/** `HH:MM` in the host timezone — the two numbers a frame label needs. */
|
|
31633
|
-
function clockOf$
|
|
31794
|
+
function clockOf$4(atMs) {
|
|
31634
31795
|
const d = new Date(atMs);
|
|
31635
31796
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
31636
31797
|
}
|
|
@@ -31653,7 +31814,7 @@ function buildSummaryAiPrompt(input, frames) {
|
|
|
31653
31814
|
const roster = frames.map((f, i) => {
|
|
31654
31815
|
const location = plainLabel(f.location ?? "");
|
|
31655
31816
|
const where = location.length > 0 ? ` (${location})` : "";
|
|
31656
|
-
return `${String(i + 1)}. ${plainLabel(f.deviceName)}${where} · ${clockOf$
|
|
31817
|
+
return `${String(i + 1)}. ${plainLabel(f.deviceName)}${where} · ${clockOf$4(f.atMs)} · ${plainVocabulary(f.className) || "subject"}`;
|
|
31657
31818
|
}).join("\n");
|
|
31658
31819
|
const described = /* @__PURE__ */ new Map();
|
|
31659
31820
|
for (const f of frames) {
|
|
@@ -32256,7 +32417,7 @@ var NO_DETECTIONS$1 = {
|
|
|
32256
32417
|
* has to check is a key somebody can get wrong. */
|
|
32257
32418
|
var BODY_SEPARATOR$1 = " · ";
|
|
32258
32419
|
/** `HH:MM` in the host timezone. */
|
|
32259
|
-
function clockOf$
|
|
32420
|
+
function clockOf$3(atMs) {
|
|
32260
32421
|
const d = new Date(atMs);
|
|
32261
32422
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
32262
32423
|
}
|
|
@@ -32270,8 +32431,8 @@ function clockOf$4(atMs) {
|
|
|
32270
32431
|
function summaryTemplateVars(input) {
|
|
32271
32432
|
return {
|
|
32272
32433
|
rule: input.ruleName,
|
|
32273
|
-
from: clockOf$
|
|
32274
|
-
to: clockOf$
|
|
32434
|
+
from: clockOf$3(input.window.startMs),
|
|
32435
|
+
to: clockOf$3(input.window.endMs),
|
|
32275
32436
|
time: ncClockText(input.texts.language, input.generatedAt),
|
|
32276
32437
|
events: String(input.tileCount),
|
|
32277
32438
|
cameras: String(input.cameraCount),
|
|
@@ -32366,11 +32527,11 @@ function summaryRecordId(ruleId, windowEndMs) {
|
|
|
32366
32527
|
/** The caption burned onto the mosaic's title band, with the same `{{var}}`
|
|
32367
32528
|
* vocabulary the title and body use. `''` is a real answer ("no caption"). */
|
|
32368
32529
|
function summaryCaptionText(input) {
|
|
32369
|
-
if (input.captionText === void 0) return `${input.ruleName} — ${clockOf$
|
|
32530
|
+
if (input.captionText === void 0) return `${input.ruleName} — ${clockOf$3(input.window.startMs)}→${clockOf$3(input.window.endMs)}`;
|
|
32370
32531
|
return renderTemplate(input.captionText, {
|
|
32371
32532
|
rule: input.ruleName,
|
|
32372
|
-
from: clockOf$
|
|
32373
|
-
to: clockOf$
|
|
32533
|
+
from: clockOf$3(input.window.startMs),
|
|
32534
|
+
to: clockOf$3(input.window.endMs)
|
|
32374
32535
|
}) ?? "";
|
|
32375
32536
|
}
|
|
32376
32537
|
/**
|
|
@@ -33302,7 +33463,7 @@ var NcSummaryProducer = class {
|
|
|
33302
33463
|
purpose: "summary",
|
|
33303
33464
|
tiles: tiles.map(({ candidate, jpeg }) => ({
|
|
33304
33465
|
jpeg,
|
|
33305
|
-
...plan.tileLabels ? { label: `${names.get(candidate.deviceId) ?? candidate.deviceId} · ${clockOf$
|
|
33466
|
+
...plan.tileLabels ? { label: `${names.get(candidate.deviceId) ?? candidate.deviceId} · ${clockOf$2(candidate.firstSeen)} · ${candidate.className}` } : {},
|
|
33306
33467
|
deviceId: candidate.deviceId,
|
|
33307
33468
|
sourceId: candidate.trackId
|
|
33308
33469
|
})),
|
|
@@ -33525,7 +33686,7 @@ var NcSummaryProducer = class {
|
|
|
33525
33686
|
}
|
|
33526
33687
|
};
|
|
33527
33688
|
/** `HH:MM` in the host timezone — the two numbers a tile label needs. */
|
|
33528
|
-
function clockOf$
|
|
33689
|
+
function clockOf$2(atMs) {
|
|
33529
33690
|
const d = new Date(atMs);
|
|
33530
33691
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
33531
33692
|
}
|
|
@@ -34026,8 +34187,6 @@ var NcCalendarEventsStore = class {
|
|
|
34026
34187
|
};
|
|
34027
34188
|
//#endregion
|
|
34028
34189
|
//#region src/notification-center/calendar-events/calendar-events-body.ts
|
|
34029
|
-
/** Visual weight on the tomorrow header — Telegram/Zentik plain text. */
|
|
34030
|
-
var CALENDAR_EVENTS_TOMORROW_MARK = "★";
|
|
34031
34190
|
/** Separates the tomorrow block from the rest of the horizon. */
|
|
34032
34191
|
var CALENDAR_EVENTS_DAY_DELIMITER = "────────";
|
|
34033
34192
|
var MAX_EVENT_LINES = 40;
|
|
@@ -34048,26 +34207,50 @@ function addLocalDays(atMs, days) {
|
|
|
34048
34207
|
d.setDate(d.getDate() + days);
|
|
34049
34208
|
return d.getTime();
|
|
34050
34209
|
}
|
|
34051
|
-
function
|
|
34052
|
-
|
|
34053
|
-
const label = new Date(dayStartMs).toLocaleDateString(void 0, {
|
|
34210
|
+
function dayDateLabel(texts, dayStartMs) {
|
|
34211
|
+
return new Intl.DateTimeFormat(texts.language, {
|
|
34054
34212
|
weekday: "short",
|
|
34055
34213
|
month: "short",
|
|
34056
34214
|
day: "numeric"
|
|
34057
|
-
});
|
|
34058
|
-
if (key === todayKey) return `Today — ${label}`;
|
|
34059
|
-
if (key === tomorrowKey) return `${CALENDAR_EVENTS_TOMORROW_MARK} Tomorrow — ${label}`;
|
|
34060
|
-
return label;
|
|
34215
|
+
}).format(new Date(dayStartMs));
|
|
34061
34216
|
}
|
|
34062
|
-
|
|
34063
|
-
|
|
34064
|
-
const
|
|
34065
|
-
return
|
|
34217
|
+
function dayHeadingFor(texts, dayStartMs, todayKey, tomorrowKey) {
|
|
34218
|
+
const key = localDayKey(dayStartMs);
|
|
34219
|
+
const date = dayDateLabel(texts, dayStartMs);
|
|
34220
|
+
if (key === todayKey) return texts.text({
|
|
34221
|
+
key: "calendar-events.day.today",
|
|
34222
|
+
vars: { date }
|
|
34223
|
+
});
|
|
34224
|
+
if (key === tomorrowKey) return texts.text({
|
|
34225
|
+
key: "calendar-events.day.tomorrow",
|
|
34226
|
+
vars: { date }
|
|
34227
|
+
});
|
|
34228
|
+
return texts.text({
|
|
34229
|
+
key: "calendar-events.day.other",
|
|
34230
|
+
vars: { date }
|
|
34231
|
+
});
|
|
34066
34232
|
}
|
|
34067
|
-
function formatEventLine(event) {
|
|
34068
|
-
const
|
|
34069
|
-
|
|
34070
|
-
|
|
34233
|
+
function formatEventLine(texts, event) {
|
|
34234
|
+
const where = event.location !== void 0 && event.location.length > 0 ? texts.text({
|
|
34235
|
+
key: "calendar-events.where",
|
|
34236
|
+
vars: { location: event.location }
|
|
34237
|
+
}) : "";
|
|
34238
|
+
if (event.allDay === true) return texts.text({
|
|
34239
|
+
key: "calendar-events.line.allDay",
|
|
34240
|
+
vars: {
|
|
34241
|
+
title: event.title,
|
|
34242
|
+
where
|
|
34243
|
+
}
|
|
34244
|
+
});
|
|
34245
|
+
const when = `${ncClockText(texts.language, event.startMs)}–${ncClockText(texts.language, event.endMs)}`;
|
|
34246
|
+
return texts.text({
|
|
34247
|
+
key: "calendar-events.line.timed",
|
|
34248
|
+
vars: {
|
|
34249
|
+
when,
|
|
34250
|
+
title: event.title,
|
|
34251
|
+
where
|
|
34252
|
+
}
|
|
34253
|
+
});
|
|
34071
34254
|
}
|
|
34072
34255
|
function groupByLocalDay(events) {
|
|
34073
34256
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -34091,8 +34274,11 @@ function groupByLocalDay(events) {
|
|
|
34091
34274
|
*
|
|
34092
34275
|
* @param nowMs — fire time; tomorrow is the local calendar day after this.
|
|
34093
34276
|
*/
|
|
34094
|
-
function formatCalendarEventsBody(events, nowMs, horizonDays) {
|
|
34095
|
-
if (events.length === 0) return
|
|
34277
|
+
function formatCalendarEventsBody(texts, events, nowMs, horizonDays) {
|
|
34278
|
+
if (events.length === 0) return texts.text({
|
|
34279
|
+
key: "calendar-events.empty",
|
|
34280
|
+
vars: { horizonDays: String(horizonDays) }
|
|
34281
|
+
});
|
|
34096
34282
|
const todayKey = localDayKey(nowMs);
|
|
34097
34283
|
const tomorrowKey = localDayKey(addLocalDays(startOfLocalDay(nowMs), 1));
|
|
34098
34284
|
const buckets = groupByLocalDay(events);
|
|
@@ -34104,17 +34290,20 @@ function formatCalendarEventsBody(events, nowMs, horizonDays) {
|
|
|
34104
34290
|
let emitted = 0;
|
|
34105
34291
|
const pushBucket = (bucket) => {
|
|
34106
34292
|
if (emitted >= MAX_EVENT_LINES) return;
|
|
34107
|
-
lines.push(dayHeadingFor(bucket.dayStartMs, todayKey, tomorrowKey));
|
|
34293
|
+
lines.push(dayHeadingFor(texts, bucket.dayStartMs, todayKey, tomorrowKey));
|
|
34108
34294
|
for (const ev of bucket.events) {
|
|
34109
34295
|
if (emitted >= MAX_EVENT_LINES) break;
|
|
34110
|
-
lines.push(formatEventLine(ev));
|
|
34296
|
+
lines.push(formatEventLine(texts, ev));
|
|
34111
34297
|
emitted += 1;
|
|
34112
34298
|
}
|
|
34113
34299
|
};
|
|
34114
34300
|
for (const bucket of lead) pushBucket(bucket);
|
|
34115
34301
|
if (lead.length > 0 && later.length > 0) lines.push(CALENDAR_EVENTS_DAY_DELIMITER);
|
|
34116
34302
|
for (const bucket of later) pushBucket(bucket);
|
|
34117
|
-
if (events.length > MAX_EVENT_LINES) lines.push(
|
|
34303
|
+
if (events.length > MAX_EVENT_LINES) lines.push(texts.text({
|
|
34304
|
+
key: "calendar-events.more",
|
|
34305
|
+
vars: { count: String(events.length - MAX_EVENT_LINES) }
|
|
34306
|
+
}));
|
|
34118
34307
|
return lines.join("\n");
|
|
34119
34308
|
}
|
|
34120
34309
|
//#endregion
|
|
@@ -34128,7 +34317,7 @@ function clockOf$1(atMs) {
|
|
|
34128
34317
|
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")}`}`;
|
|
34129
34318
|
}
|
|
34130
34319
|
function derivedBody$1(input) {
|
|
34131
|
-
return formatCalendarEventsBody(input.events, input.eventWindow.startMs, input.horizonDays);
|
|
34320
|
+
return formatCalendarEventsBody(input.texts, input.events, input.eventWindow.startMs, input.horizonDays);
|
|
34132
34321
|
}
|
|
34133
34322
|
function templateVars(input) {
|
|
34134
34323
|
return {
|
|
@@ -34154,7 +34343,11 @@ function paramsFor$1(target) {
|
|
|
34154
34343
|
function buildCalendarEventsOutboxInputs(input) {
|
|
34155
34344
|
const vars = templateVars(input);
|
|
34156
34345
|
const optedOut = new Set(input.disabledTargetIds ?? []);
|
|
34157
|
-
const title =
|
|
34346
|
+
const title = input.texts.text({
|
|
34347
|
+
key: "calendar-events.title",
|
|
34348
|
+
template: input.template?.title,
|
|
34349
|
+
vars
|
|
34350
|
+
});
|
|
34158
34351
|
const body = renderTemplate(input.template?.body, vars) ?? derivedBody$1(input);
|
|
34159
34352
|
const recordId = calendarEventsRecordId(input.ruleId, input.scheduleWindow.endMs);
|
|
34160
34353
|
return input.targets.filter((t) => !optedOut.has(t.targetId)).map((target) => {
|
|
@@ -34303,6 +34496,7 @@ var NcCalendarEventsProducer = class {
|
|
|
34303
34496
|
}
|
|
34304
34497
|
async enqueue(rule, scheduleWindow, eventWindow, calendarIds, events) {
|
|
34305
34498
|
const inputs = buildCalendarEventsOutboxInputs({
|
|
34499
|
+
texts: this.deps.texts,
|
|
34306
34500
|
ruleId: rule.id,
|
|
34307
34501
|
ruleName: rule.name,
|
|
34308
34502
|
scheduleWindow,
|
|
@@ -34317,7 +34511,7 @@ var NcCalendarEventsProducer = class {
|
|
|
34317
34511
|
priority: rule.priority,
|
|
34318
34512
|
...rule.template !== void 0 ? { template: rule.template } : {}
|
|
34319
34513
|
});
|
|
34320
|
-
|
|
34514
|
+
await this.deps.outbox.enqueue(inputs);
|
|
34321
34515
|
this.deps.logger.info("calendar-events delivered", { meta: {
|
|
34322
34516
|
ruleId: rule.id,
|
|
34323
34517
|
events: events.length,
|
|
@@ -35676,16 +35870,25 @@ function stateItem(row, lookup) {
|
|
|
35676
35870
|
/**
|
|
35677
35871
|
* THE RECONCILE (D647 review I2, D8): a ledger row is a memory, never the
|
|
35678
35872
|
* truth. A device item is listed only while the device EXISTS in the directory
|
|
35679
|
-
*
|
|
35680
|
-
*
|
|
35681
|
-
*
|
|
35682
|
-
*
|
|
35873
|
+
* — and an "offline" one only while the device manager does not call it
|
|
35874
|
+
* online. A node item only while the node is known to a complete node
|
|
35875
|
+
* directory (a forgotten node is gone), and an "offline" one only while the
|
|
35876
|
+
* directory does not call it online.
|
|
35877
|
+
*
|
|
35878
|
+
* Accessories are silenced only on the liveness / switched-off section
|
|
35879
|
+
* (`device-state`). Battery, consumable and tank alerts ride the SAME
|
|
35880
|
+
* reconcile, and a vacuum that is a child of its Dreame container
|
|
35881
|
+
* (`parentDeviceId` set) still owns those alerts — filtering every accessory
|
|
35882
|
+
* here dropped every tank line of the 2026-10-05 evening summary for
|
|
35883
|
+
* deviceId 4385 while the episode was open and the instant path had already
|
|
35884
|
+
* held `device-tank-low` for that rule.
|
|
35683
35885
|
*/
|
|
35684
35886
|
function stillTrue(item, ports) {
|
|
35685
35887
|
const deviceId = item.subject.deviceId;
|
|
35686
35888
|
if (deviceId !== void 0) {
|
|
35687
35889
|
const identity = ports.lookup(deviceId);
|
|
35688
|
-
if (identity === void 0
|
|
35890
|
+
if (identity === void 0) return false;
|
|
35891
|
+
if (item.section === "device-state" && isAccessoryIdentity(identity)) return false;
|
|
35689
35892
|
if (item.subject.kind === "device-offline" && identity.online === true) return false;
|
|
35690
35893
|
}
|
|
35691
35894
|
const nodeId = item.subject.nodeId;
|
|
@@ -38794,6 +38997,7 @@ var NotificationCenter = class {
|
|
|
38794
38997
|
store: this.calendarEventsRules,
|
|
38795
38998
|
outbox: this.outbox,
|
|
38796
38999
|
calendars: calendarsPort,
|
|
39000
|
+
texts: this.texts,
|
|
38797
39001
|
logger: this.logger.child("calendar-events"),
|
|
38798
39002
|
now: this.now
|
|
38799
39003
|
});
|
|
@@ -40964,8 +41168,14 @@ var NotificationCenter = class {
|
|
|
40964
41168
|
}
|
|
40965
41169
|
if (kind !== "alarm-armed" && kind !== "alarm-arming") return null;
|
|
40966
41170
|
if (mode === void 0) return null;
|
|
40967
|
-
const coverage = this.coverageFor(panel).find((c) => c.mode === mode)
|
|
40968
|
-
|
|
41171
|
+
const coverage = this.coverageFor(panel).find((c) => c.mode === mode) ?? {
|
|
41172
|
+
mode,
|
|
41173
|
+
ruleCount: 0,
|
|
41174
|
+
allDevices: false,
|
|
41175
|
+
allDevicesExcept: [],
|
|
41176
|
+
deviceIds: [],
|
|
41177
|
+
skippedDevices: []
|
|
41178
|
+
};
|
|
40969
41179
|
const name = this.deviceNameFromDirectory();
|
|
40970
41180
|
const bypassed = panel.excludedDevices();
|
|
40971
41181
|
const text = kind === "alarm-arming" ? buildArmingAnnouncement(coverage, transition.seconds ?? 0, name, bypassed) : buildArmAnnouncement(coverage, name, bypassed);
|
|
@@ -57616,36 +57826,68 @@ var DEFAULT_STATE_ANALYZER_CONFIG = {
|
|
|
57616
57826
|
stillWindowMs: DEFAULT_STILL_WINDOW_MS,
|
|
57617
57827
|
stillSpanFraction: DEFAULT_STILL_SPAN_FRACTION
|
|
57618
57828
|
};
|
|
57829
|
+
function shortSideOf(bbox) {
|
|
57830
|
+
return Math.min(bbox.w, bbox.h);
|
|
57831
|
+
}
|
|
57619
57832
|
var StateAnalyzer = class {
|
|
57620
57833
|
config;
|
|
57621
57834
|
states = /* @__PURE__ */ new Map();
|
|
57835
|
+
parked = /* @__PURE__ */ new Map();
|
|
57622
57836
|
constructor(config = {}) {
|
|
57623
57837
|
this.config = {
|
|
57624
57838
|
...DEFAULT_STATE_ANALYZER_CONFIG,
|
|
57625
57839
|
...config
|
|
57626
57840
|
};
|
|
57627
57841
|
}
|
|
57628
|
-
/**
|
|
57842
|
+
/**
|
|
57843
|
+
* D683: a fused continuation keeps its movement state under the survivor id.
|
|
57844
|
+
*
|
|
57845
|
+
* The absorbed piece is the live continuation, but a still subject that L1
|
|
57846
|
+
* split must not lose the dwell the survivor already paid — `rename` MERGES
|
|
57847
|
+
* the stillness clocks when the geometry across the gap is still still, and
|
|
57848
|
+
* only takes the live piece alone when the subject actually moved.
|
|
57849
|
+
*/
|
|
57629
57850
|
rename(from, to) {
|
|
57630
|
-
|
|
57851
|
+
const live = this.states.get(from) ?? this.parked.get(from)?.state;
|
|
57852
|
+
if (live === void 0) return;
|
|
57853
|
+
this.states.delete(from);
|
|
57854
|
+
this.parked.delete(from);
|
|
57855
|
+
const survivor = this.states.get(to) ?? this.parked.get(to)?.state;
|
|
57856
|
+
this.parked.delete(to);
|
|
57857
|
+
if (survivor === void 0) {
|
|
57858
|
+
this.states.set(to, live);
|
|
57859
|
+
return;
|
|
57860
|
+
}
|
|
57861
|
+
this.states.set(to, this.mergeContinuation(survivor, live));
|
|
57631
57862
|
}
|
|
57632
57863
|
analyze(tracks, timestamp) {
|
|
57864
|
+
this.pruneParked(timestamp);
|
|
57633
57865
|
const currentIds = new Set(tracks.map((t) => t.trackId));
|
|
57634
57866
|
const results = [];
|
|
57635
57867
|
for (const track of tracks) {
|
|
57636
57868
|
let ts = this.states.get(track.trackId);
|
|
57869
|
+
if (ts === void 0) {
|
|
57870
|
+
const parked = this.parked.get(track.trackId);
|
|
57871
|
+
if (parked !== void 0) {
|
|
57872
|
+
this.parked.delete(track.trackId);
|
|
57873
|
+
ts = parked.state;
|
|
57874
|
+
this.states.set(track.trackId, ts);
|
|
57875
|
+
}
|
|
57876
|
+
}
|
|
57637
57877
|
const center = bboxCentroid({
|
|
57638
57878
|
x: track.bbox.x,
|
|
57639
57879
|
y: track.bbox.y,
|
|
57640
57880
|
w: track.bbox.w,
|
|
57641
57881
|
h: track.bbox.h
|
|
57642
57882
|
});
|
|
57883
|
+
const shortSide = shortSideOf(track.bbox);
|
|
57643
57884
|
if (!ts) {
|
|
57644
57885
|
ts = {
|
|
57645
57886
|
enteredAt: timestamp,
|
|
57646
57887
|
stationarySince: void 0,
|
|
57647
57888
|
totalDistancePx: 0,
|
|
57648
57889
|
lastPosition: center,
|
|
57890
|
+
lastShortSide: shortSide,
|
|
57649
57891
|
frameCount: 1,
|
|
57650
57892
|
recent: [{
|
|
57651
57893
|
x: center.x,
|
|
@@ -57660,14 +57902,14 @@ var StateAnalyzer = class {
|
|
|
57660
57902
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
57661
57903
|
ts.totalDistancePx += dist;
|
|
57662
57904
|
ts.lastPosition = center;
|
|
57905
|
+
ts.lastShortSide = shortSide;
|
|
57663
57906
|
ts.frameCount++;
|
|
57664
57907
|
ts.recent.push({
|
|
57665
57908
|
x: center.x,
|
|
57666
57909
|
y: center.y,
|
|
57667
57910
|
at: timestamp
|
|
57668
57911
|
});
|
|
57669
|
-
|
|
57670
|
-
while (ts.recent.length > 2 && ts.recent[1].at < cutoff) ts.recent.shift();
|
|
57912
|
+
this.trimRecent(ts, timestamp);
|
|
57671
57913
|
}
|
|
57672
57914
|
const velocity = track.velocity ? Math.sqrt(track.velocity.dx ** 2 + track.velocity.dy ** 2) : 0;
|
|
57673
57915
|
const dwellTimeMs = timestamp - ts.enteredAt;
|
|
@@ -57703,10 +57945,62 @@ var StateAnalyzer = class {
|
|
|
57703
57945
|
dwellTimeMs: timestamp - ts.enteredAt
|
|
57704
57946
|
});
|
|
57705
57947
|
this.states.delete(trackId);
|
|
57948
|
+
this.parked.set(trackId, {
|
|
57949
|
+
state: ts,
|
|
57950
|
+
parkedAt: timestamp
|
|
57951
|
+
});
|
|
57706
57952
|
}
|
|
57707
57953
|
return results;
|
|
57708
57954
|
}
|
|
57709
57955
|
/**
|
|
57956
|
+
* Fold a live continuation into its ended survivor.
|
|
57957
|
+
*
|
|
57958
|
+
* Stillness is what the parked-object ledger votes on (`idle` only). A gap
|
|
57959
|
+
* past L1's coast budget that L2 later heals must not restart that clock
|
|
57960
|
+
* when the centroid never left the still span. A continuation that DID move
|
|
57961
|
+
* drops the survivor's `stationarySince` so a walk after a pause is still
|
|
57962
|
+
* `moving`.
|
|
57963
|
+
*/
|
|
57964
|
+
mergeContinuation(survivor, live) {
|
|
57965
|
+
const gapTravel = Math.hypot(live.lastPosition.x - survivor.lastPosition.x, live.lastPosition.y - survivor.lastPosition.y);
|
|
57966
|
+
const subject = Math.max(survivor.lastShortSide, live.lastShortSide);
|
|
57967
|
+
const stillAcrossGap = survivor.stationarySince !== void 0 && subject > 0 && gapTravel <= subject * this.config.stillSpanFraction;
|
|
57968
|
+
const lastAt = live.recent[live.recent.length - 1]?.at;
|
|
57969
|
+
if (stillAcrossGap && survivor.stationarySince !== void 0 && lastAt !== void 0) {
|
|
57970
|
+
const anchorAt = lastAt - this.config.stillWindowMs;
|
|
57971
|
+
const recent = [{
|
|
57972
|
+
x: live.lastPosition.x,
|
|
57973
|
+
y: live.lastPosition.y,
|
|
57974
|
+
at: anchorAt
|
|
57975
|
+
}, ...live.recent.filter((s) => s.at > anchorAt)];
|
|
57976
|
+
return {
|
|
57977
|
+
enteredAt: Math.min(survivor.enteredAt, live.enteredAt),
|
|
57978
|
+
stationarySince: live.stationarySince !== void 0 ? Math.min(survivor.stationarySince, live.stationarySince) : survivor.stationarySince,
|
|
57979
|
+
totalDistancePx: survivor.totalDistancePx + live.totalDistancePx,
|
|
57980
|
+
lastPosition: live.lastPosition,
|
|
57981
|
+
lastShortSide: live.lastShortSide,
|
|
57982
|
+
frameCount: Math.max(survivor.frameCount + live.frameCount, this.config.enteringFrames + 1),
|
|
57983
|
+
recent
|
|
57984
|
+
};
|
|
57985
|
+
}
|
|
57986
|
+
return {
|
|
57987
|
+
enteredAt: live.enteredAt,
|
|
57988
|
+
stationarySince: live.stationarySince,
|
|
57989
|
+
totalDistancePx: survivor.totalDistancePx + live.totalDistancePx,
|
|
57990
|
+
lastPosition: live.lastPosition,
|
|
57991
|
+
lastShortSide: live.lastShortSide,
|
|
57992
|
+
frameCount: live.frameCount,
|
|
57993
|
+
recent: [...live.recent]
|
|
57994
|
+
};
|
|
57995
|
+
}
|
|
57996
|
+
trimRecent(ts, timestamp) {
|
|
57997
|
+
const cutoff = timestamp - this.config.stillWindowMs;
|
|
57998
|
+
while (ts.recent.length > 2 && ts.recent[1].at < cutoff) ts.recent.shift();
|
|
57999
|
+
}
|
|
58000
|
+
pruneParked(timestamp) {
|
|
58001
|
+
for (const [id, parked] of this.parked) if (timestamp - parked.parkedAt > LINK_WINDOW_MS) this.parked.delete(id);
|
|
58002
|
+
}
|
|
58003
|
+
/**
|
|
57710
58004
|
* The SPEED half of the settled test.
|
|
57711
58005
|
*
|
|
57712
58006
|
* Uncalibrated (`stillSpeedFracPerSec` undefined) this is the original line
|
|
@@ -57768,6 +58062,7 @@ var StateAnalyzer = class {
|
|
|
57768
58062
|
}
|
|
57769
58063
|
reset() {
|
|
57770
58064
|
this.states.clear();
|
|
58065
|
+
this.parked.clear();
|
|
57771
58066
|
}
|
|
57772
58067
|
};
|
|
57773
58068
|
//#endregion
|
|
@@ -86906,23 +87201,19 @@ function sweepOne(deps, deviceId) {
|
|
|
86906
87201
|
}
|
|
86907
87202
|
//#endregion
|
|
86908
87203
|
//#region src/pipeline-analytics/pipeline/stationary/stationary-zones.ts
|
|
87204
|
+
/** Shared with every call — ZoneEngine is stateless for annotateDetection. */
|
|
87205
|
+
var ENGINE = new ZoneEngine();
|
|
86909
87206
|
/**
|
|
86910
|
-
* Zone ids whose 0–1
|
|
86911
|
-
*
|
|
87207
|
+
* Zone ids whose bbox overlap exceeds `minOverlap` (0–1 fraction of the
|
|
87208
|
+
* detection's own area) — identical to {@link ZoneEngine.annotateDetection}.
|
|
87209
|
+
* Empty when the entry has no frame dims or no zone meets the bar.
|
|
87210
|
+
*
|
|
87211
|
+
* `minOverlap` is required: callers pass `TrackingSettings.zoneMembershipMinOverlap`
|
|
87212
|
+
* so parked and live never disagree on the threshold.
|
|
86912
87213
|
*/
|
|
86913
|
-
function computeStationaryEntryZones(entry, zones) {
|
|
87214
|
+
function computeStationaryEntryZones(entry, zones, minOverlap) {
|
|
86914
87215
|
if (entry.frameWidth <= 0 || entry.frameHeight <= 0 || zones.length === 0) return [];
|
|
86915
|
-
|
|
86916
|
-
const point = {
|
|
86917
|
-
x: centroidPx.x / entry.frameWidth,
|
|
86918
|
-
y: centroidPx.y / entry.frameHeight
|
|
86919
|
-
};
|
|
86920
|
-
const matched = [];
|
|
86921
|
-
for (const zone of zones) {
|
|
86922
|
-
if (zone.polygon.length < 3) continue;
|
|
86923
|
-
if (pointInPolygon$1(point, zone.polygon)) matched.push(zone.id);
|
|
86924
|
-
}
|
|
86925
|
-
return matched;
|
|
87216
|
+
return ENGINE.annotateDetection(entry.bbox, zones, entry.frameWidth, entry.frameHeight, void 0, void 0, void 0, minOverlap).map((m) => m.zoneId);
|
|
86926
87217
|
}
|
|
86927
87218
|
//#endregion
|
|
86928
87219
|
//#region src/pipeline-analytics/pipeline/package-drop-detector.ts
|
|
@@ -87022,15 +87313,17 @@ var PackageDropDetector = class {
|
|
|
87022
87313
|
}
|
|
87023
87314
|
const ruleZoneIds = new Set(rules.flatMap((r) => r.zoneIds));
|
|
87024
87315
|
const zones = await this.deps.resolveZones(entry.deviceId);
|
|
87025
|
-
const
|
|
87316
|
+
const minOverlap = await this.deps.resolveZoneMembershipMinOverlap(entry.deviceId);
|
|
87317
|
+
const entryZones = computeStationaryEntryZones(entry, zones, minOverlap);
|
|
87026
87318
|
const hitZones = entryZones.filter((z) => ruleZoneIds.has(z));
|
|
87027
87319
|
if (hitZones.length === 0) {
|
|
87028
|
-
this.deps.logger.info("package gate 2/5 zone —
|
|
87320
|
+
this.deps.logger.info("package gate 2/5 zone — no package zone meets membership overlap", {
|
|
87029
87321
|
tags,
|
|
87030
87322
|
meta: {
|
|
87031
87323
|
entryId: entry.id,
|
|
87032
87324
|
bbox: entry.bbox,
|
|
87033
87325
|
entryZones,
|
|
87326
|
+
minOverlap,
|
|
87034
87327
|
ruleZoneIds: [...ruleZoneIds],
|
|
87035
87328
|
zonesOnDevice: zones.length
|
|
87036
87329
|
}
|
|
@@ -100802,7 +101095,7 @@ function buildDetectionSettingsSections() {
|
|
|
100802
101095
|
type: "number",
|
|
100803
101096
|
key: "zoneMembershipMinOverlap",
|
|
100804
101097
|
label: "Zone membership minimum overlap",
|
|
100805
|
-
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.",
|
|
101098
|
+
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.",
|
|
100806
101099
|
min: 0,
|
|
100807
101100
|
max: 1,
|
|
100808
101101
|
step: .05,
|
|
@@ -104023,14 +104316,21 @@ var ZoneAnalyticsProvider = class {
|
|
|
104023
104316
|
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
104024
104317
|
});
|
|
104025
104318
|
}
|
|
104319
|
+
const minOverlap = await this.resolveMinOverlap(deviceId);
|
|
104026
104320
|
const ts = mostRecentStationaryConfirmedAt(entries) || Date.now();
|
|
104027
104321
|
return buildStationarySnapshot({
|
|
104028
104322
|
deviceId,
|
|
104029
104323
|
entries,
|
|
104030
104324
|
zones,
|
|
104031
|
-
timestamp: ts
|
|
104325
|
+
timestamp: ts,
|
|
104326
|
+
minOverlap
|
|
104032
104327
|
});
|
|
104033
104328
|
}
|
|
104329
|
+
/** Tracking membership bar — schema default when the host did not wire it. */
|
|
104330
|
+
async resolveMinOverlap(deviceId) {
|
|
104331
|
+
if (this.ctx.resolveZoneMembershipMinOverlap === void 0) return TRACKING_DEFAULTS.zoneMembershipMinOverlap;
|
|
104332
|
+
return this.ctx.resolveZoneMembershipMinOverlap(deviceId);
|
|
104333
|
+
}
|
|
104034
104334
|
/**
|
|
104035
104335
|
* Baseline sampler tick: for every device with parked objects, append a
|
|
104036
104336
|
* hydrated sample to the history ring at the CURRENT time — but only when a
|
|
@@ -104048,11 +104348,13 @@ var ZoneAnalyticsProvider = class {
|
|
|
104048
104348
|
try {
|
|
104049
104349
|
zones = await this.ctx.resolveZones?.(deviceId) ?? [];
|
|
104050
104350
|
} catch {}
|
|
104351
|
+
const minOverlap = await this.resolveMinOverlap(deviceId);
|
|
104051
104352
|
const snapshot = buildStationarySnapshot({
|
|
104052
104353
|
deviceId,
|
|
104053
104354
|
entries,
|
|
104054
104355
|
zones,
|
|
104055
|
-
timestamp: now
|
|
104356
|
+
timestamp: now,
|
|
104357
|
+
minOverlap
|
|
104056
104358
|
});
|
|
104057
104359
|
if (snapshot) {
|
|
104058
104360
|
this.appendHistory(deviceId, snapshot);
|
|
@@ -104209,18 +104511,18 @@ function mostRecentStationaryConfirmedAt(entries) {
|
|
|
104209
104511
|
/**
|
|
104210
104512
|
* Build an occupancy snapshot from parked-object registry entries alone —
|
|
104211
104513
|
* used when no live inference frame is available (fresh respawn, camera
|
|
104212
|
-
* detached). Each entry is folded into the frame aggregate AND attributed
|
|
104213
|
-
*
|
|
104214
|
-
*
|
|
104215
|
-
*
|
|
104216
|
-
*
|
|
104514
|
+
* detached). Each entry is folded into the frame aggregate AND attributed via
|
|
104515
|
+
* {@link computeStationaryEntryZones} (same `ZoneEngine` + `minOverlap` as
|
|
104516
|
+
* live stamps), so a zone drawn over a parked car reports a count of 1.
|
|
104517
|
+
* Returns `null` for an empty entry list. Reuses {@link computeSnapshot} —
|
|
104518
|
+
* the SAME aggregation the live frame path runs.
|
|
104217
104519
|
*/
|
|
104218
104520
|
function buildStationarySnapshot(input) {
|
|
104219
104521
|
if (input.entries.length === 0) return null;
|
|
104220
104522
|
const tracked = input.entries.map((e) => ({
|
|
104221
104523
|
trackId: `stationary:${e.id}`,
|
|
104222
104524
|
className: e.className,
|
|
104223
|
-
zones: computeStationaryEntryZones(e, input.zones),
|
|
104525
|
+
zones: computeStationaryEntryZones(e, input.zones, input.minOverlap),
|
|
104224
104526
|
standing: true
|
|
104225
104527
|
}));
|
|
104226
104528
|
const first = input.entries[0];
|
|
@@ -107043,6 +107345,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
107043
107345
|
resolveZones: (deviceId) => this.resolveDeviceZones(deviceId),
|
|
107044
107346
|
resolvePackageRules: (deviceId) => this.resolveDevicePackageRules(deviceId),
|
|
107045
107347
|
resolveSettings: (deviceId) => this.resolveDevicePackageDropSettings(deviceId),
|
|
107348
|
+
resolveZoneMembershipMinOverlap: async (deviceId) => (await this.resolveDeviceTrackingSettings(deviceId)).zoneMembershipMinOverlap,
|
|
107046
107349
|
onPersisted: (ev, phase) => this.notificationCenter?.onPackageEventPersisted(ev, phase),
|
|
107047
107350
|
onError: (scope, err) => logger.warn("package-drop detector error", { meta: {
|
|
107048
107351
|
scope,
|
|
@@ -107338,6 +107641,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
107338
107641
|
listStationaryObjects: (deviceId) => this.stationaryRegistry?.listViews(deviceId) ?? [],
|
|
107339
107642
|
listStationaryDeviceIds: () => this.stationaryRegistry?.deviceIds() ?? [],
|
|
107340
107643
|
resolveZones: (deviceId) => this.resolveDeviceZones(deviceId),
|
|
107644
|
+
resolveZoneMembershipMinOverlap: async (deviceId) => (await this.resolveDeviceTrackingSettings(deviceId)).zoneMembershipMinOverlap,
|
|
107341
107645
|
onSnapshot: (deviceId, snapshot, evidence) => this.notificationCenter?.observeOccupancy(deviceId, snapshot, evidence)
|
|
107342
107646
|
});
|
|
107343
107647
|
this.zoneAnalytics = zoneAnalytics;
|
|
@@ -109674,7 +109978,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
109674
109978
|
const stationaryAsTracked = stationaryViews.map((v) => ({
|
|
109675
109979
|
trackId: `stationary:${v.id}`,
|
|
109676
109980
|
className: v.className,
|
|
109677
|
-
zones: computeStationaryEntryZones(v, liveZones),
|
|
109981
|
+
zones: computeStationaryEntryZones(v, liveZones, trk.zoneMembershipMinOverlap),
|
|
109678
109982
|
standing: true
|
|
109679
109983
|
}));
|
|
109680
109984
|
this.zoneAnalytics?.recordFrame({
|